<!DOCTYPE html>
<html class="writer-html5" lang="zh" >
<head>
  <meta charset="utf-8" /><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />

  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Ch7-6 拷贝图像到屏幕 &mdash; EasyVulkan</title>
      <link rel="stylesheet" href="_static/pygments.css" type="text/css" />
      <link rel="stylesheet" href="_static/css/theme.css" type="text/css" />
      <link rel="stylesheet" href="_static/theme.css" type="text/css" />
  <!--[if lt IE 9]>
    <script src="_static/js/html5shiv.min.js"></script>
  <![endif]-->
  
        <script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
        <script src="_static/jquery.js"></script>
        <script src="_static/underscore.js"></script>
        <script src="_static/_sphinx_javascript_frameworks_compat.js"></script>
        <script src="_static/doctools.js"></script>
    <script src="_static/js/theme.js"></script>
    <link rel="index" title="索引" href="genindex.html" />
    <link rel="search" title="搜索" href="search.html" />
    <link rel="next" title="Ch7-7 使用贴图" href="Ch7-7%20%E4%BD%BF%E7%94%A8%E8%B4%B4%E5%9B%BE.html" />
    <link rel="prev" title="Ch7-5 初识Uniform缓冲区" href="Ch7-5%20%E5%88%9D%E8%AF%86Uniform%E7%BC%93%E5%86%B2%E5%8C%BA.html" /> 
</head>

<body class="wy-body-for-nav"> 
  <div class="wy-grid-for-nav">
    <nav data-toggle="wy-nav-shift" class="wy-nav-side">
      <div class="wy-side-scroll">
        <div class="wy-side-nav-search" >
            <a href="index.html" class="icon icon-home"> EasyVulkan
            <img src="_static/logo1.png" class="logo" alt="Logo"/>
          </a>
<div role="search">
  <form id="rtd-search-form" class="wy-form" action="search.html" method="get">
    <input type="text" name="q" placeholder="在文档中搜索" />
    <input type="hidden" name="check_keywords" value="yes" />
    <input type="hidden" name="area" value="default" />
  </form>
</div>
        </div><div class="wy-menu wy-menu-vertical" data-spy="affix" role="navigation" aria-label="Navigation menu">
              <p class="caption" role="heading"><span class="caption-text">第一章 初始化</span></p>
<ul>
<li class="toctree-l1"><a class="reference internal" href="Ch1-0%20%E5%87%86%E5%A4%87%E5%B7%A5%E4%BD%9C.html">Ch1-0 准备工作</a></li>
<li class="toctree-l1"><a class="reference internal" href="Ch1-1%20%E5%88%9B%E5%BB%BAGLFW%E7%AA%97%E5%8F%A3.html">Ch1-1 创建GLFW窗口</a></li>
<li class="toctree-l1"><a class="reference internal" href="Ch1-2%20%E5%88%9D%E5%A7%8B%E5%8C%96%E6%B5%81%E7%A8%8B.html">Ch1-2 初始化流程</a></li>
<li class="toctree-l1"><a class="reference internal" href="Ch1-3%20%E5%88%9B%E5%BB%BAVK%E5%AE%9E%E4%BE%8B%E4%B8%8E%E9%80%BB%E8%BE%91%E8%AE%BE%E5%A4%87.html">Ch1-3 创建VK实例与逻辑设备</a></li>
<li class="toctree-l1"><a class="reference internal" href="Ch1-4%20%E5%88%9B%E5%BB%BA%E4%BA%A4%E6%8D%A2%E9%93%BE.html">Ch1-4 创建交换链</a></li>
</ul>
<p class="caption" role="heading"><span class="caption-text">第二章 绘制一个三角形</span></p>
<ul>
<li class="toctree-l1"><a class="reference internal" href="Ch2-0%20%E4%BB%A3%E7%A0%81%E6%95%B4%E7%90%86%E5%8F%8A%E4%B8%80%E4%BA%9B%E8%BE%85%E5%8A%A9%E7%B1%BB.html">Ch2-0 代码整理及一些辅助类</a></li>
<li class="toctree-l1"><a class="reference internal" href="Ch2-1%20Rendering%20Loop.html">Ch2-1 Rendering Loop</a></li>
<li class="toctree-l1"><a class="reference internal" href="Ch2-2%20%E5%88%9B%E5%BB%BA%E6%B8%B2%E6%9F%93%E9%80%9A%E9%81%93%E5%92%8C%E5%B8%A7%E7%BC%93%E5%86%B2.html">Ch2-2 创建渲染通道和帧缓冲</a></li>
<li class="toctree-l1"><a class="reference internal" href="Ch2-3%20%E5%88%9B%E5%BB%BA%E7%AE%A1%E7%BA%BF%E5%B9%B6%E7%BB%98%E5%88%B6%E4%B8%89%E8%A7%92%E5%BD%A2.html">Ch2-3 创建管线并绘制三角形</a></li>
</ul>
<p class="caption" role="heading"><span class="caption-text">第三章 纵观Vulkan</span></p>
<ul>
<li class="toctree-l1"><a class="reference internal" href="Ch3-1%20%E5%90%8C%E6%AD%A5%E5%8E%9F%E8%AF%AD.html">Ch3-1 同步原语</a></li>
<li class="toctree-l1"><a class="reference internal" href="Ch3-2%20%E5%9B%BE%E5%83%8F%E4%B8%8E%E7%BC%93%E5%86%B2%E5%8C%BA.html">Ch3-2 图像与缓冲区</a></li>
<li class="toctree-l1"><a class="reference internal" href="Ch3-3%20%E7%AE%A1%E7%BA%BF%E5%B8%83%E5%B1%80%E5%92%8C%E7%AE%A1%E7%BA%BF.html">Ch3-3 管线布局和管线</a></li>
<li class="toctree-l1"><a class="reference internal" href="Ch3-4%20%E6%B8%B2%E6%9F%93%E9%80%9A%E9%81%93%E5%92%8C%E5%B8%A7%E7%BC%93%E5%86%B2.html">Ch3-4 渲染通道和帧缓冲</a></li>
<li class="toctree-l1"><a class="reference internal" href="Ch3-5%20%E5%91%BD%E4%BB%A4%E7%BC%93%E5%86%B2%E5%8C%BA.html">Ch3-5 命令缓冲区</a></li>
<li class="toctree-l1"><a class="reference internal" href="Ch3-6%20%E6%8F%8F%E8%BF%B0%E7%AC%A6.html">Ch3-6 描述符</a></li>
<li class="toctree-l1"><a class="reference internal" href="Ch3-7%20%E9%87%87%E6%A0%B7%E5%99%A8.html">Ch3-7 采样器</a></li>
<li class="toctree-l1"><a class="reference internal" href="Ch3-8%20%E6%9F%A5%E8%AF%A2.html">Ch3-8 查询</a></li>
</ul>
<p class="caption" role="heading"><span class="caption-text">第四章 着色器</span></p>
<ul>
<li class="toctree-l1"><a class="reference internal" href="Ch4-1%20%E7%9D%80%E8%89%B2%E5%99%A8%E6%A8%A1%E7%BB%84.html">Ch4-1 着色器模组</a></li>
<li class="toctree-l1"><a class="reference internal" href="Ch4-2%20%E9%A1%B6%E7%82%B9%E7%9D%80%E8%89%B2%E5%99%A8.html">Ch4-2 顶点着色器</a></li>
<li class="toctree-l1"><a class="reference internal" href="Ch4-3%20%E7%89%87%E6%AE%B5%E7%9D%80%E8%89%B2%E5%99%A8.html">Ch4-3 片段着色器</a></li>
</ul>
<p class="caption" role="heading"><span class="caption-text">第五章 封装常用对象</span></p>
<ul>
<li class="toctree-l1"><a class="reference internal" href="Ch5-0%20VKBase%2B.h.html">Ch5-0 VKBase+.h</a></li>
<li class="toctree-l1"><a class="reference internal" href="Ch5-1%20%E5%90%84%E7%A7%8D%E7%BC%93%E5%86%B2%E5%8C%BA.html">Ch5-1 各种缓冲区</a></li>
<li class="toctree-l1"><a class="reference internal" href="Ch5-2%202D%E8%B4%B4%E5%9B%BE%E5%8F%8A%E7%94%9F%E6%88%90Mipmap.html">Ch5-2 2D贴图及生成Mipmap</a></li>
<li class="toctree-l1"><a class="reference internal" href="Ch5-3%202D%E8%B4%B4%E5%9B%BE%E6%95%B0%E7%BB%84.html">Ch5-3 2D贴图数组</a></li>
</ul>
<p class="caption" role="heading"><span class="caption-text">第六章 进阶Vulkan</span></p>
<ul>
<li class="toctree-l1"><a class="reference internal" href="Ch6-0%20%E4%BD%BF%E7%94%A8%E6%96%B0%E7%89%88%E6%9C%AC%E7%89%B9%E6%80%A7.html">Ch6-0 使用新版本特性</a></li>
<li class="toctree-l1"><a class="reference internal" href="Ch6-1%20%E6%97%A0%E5%9B%BE%E5%83%8F%E5%B8%A7%E7%BC%93%E5%86%B2.html">Ch6-1 无图像帧缓冲</a></li>
<li class="toctree-l1"><a class="reference internal" href="Ch6-2%20%E5%8A%A8%E6%80%81%E6%B8%B2%E6%9F%93.html">Ch6-2 动态渲染</a></li>
</ul>
<p class="caption" role="heading"><span class="caption-text">第七章 基础示例</span></p>
<ul class="current">
<li class="toctree-l1"><a class="reference internal" href="Ch7-1%20%E5%88%9D%E8%AF%86%E9%A1%B6%E7%82%B9%E7%BC%93%E5%86%B2%E5%8C%BA.html">Ch7-1 初识顶点缓冲区</a></li>
<li class="toctree-l1"><a class="reference internal" href="Ch7-2%20%E5%88%9D%E8%AF%86%E7%B4%A2%E5%BC%95%E7%BC%93%E5%86%B2%E5%8C%BA.html">Ch7-2 初识索引缓冲区</a></li>
<li class="toctree-l1"><a class="reference internal" href="Ch7-3%20%E5%88%9D%E8%AF%86%E5%AE%9E%E4%BE%8B%E5%8C%96%E7%BB%98%E5%88%B6.html">Ch7-3 初识实例化绘制</a></li>
<li class="toctree-l1"><a class="reference internal" href="Ch7-4%20%E5%88%9D%E8%AF%86Push%20Constant.html">Ch7-4 初识Push Constant</a></li>
<li class="toctree-l1"><a class="reference internal" href="Ch7-5%20%E5%88%9D%E8%AF%86Uniform%E7%BC%93%E5%86%B2%E5%8C%BA.html">Ch7-5 初识Uniform缓冲区</a></li>
<li class="toctree-l1 current"><a class="current reference internal" href="#">Ch7-6 拷贝图像到屏幕</a><ul>
<li class="toctree-l2"><a class="reference internal" href="#id1">将图像拷贝到交换链图像的流程</a></li>
<li class="toctree-l2"><a class="reference internal" href="#id2">读取图像数据</a></li>
<li class="toctree-l2"><a class="reference internal" href="#bootscreen">BootScreen</a></li>
<li class="toctree-l2"><a class="reference internal" href="#id3">图像操作</a><ul>
<li class="toctree-l3"><a class="reference internal" href="#cmdcopybuffertoimage">CmdCopyBufferToImage</a></li>
<li class="toctree-l3"><a class="reference internal" href="#cmdblitimage">CmdBlitImage</a></li>
</ul>
</li>
<li class="toctree-l2"><a class="reference internal" href="#id4">从暂存缓冲区到交换链图像</a></li>
<li class="toctree-l2"><a class="reference internal" href="#id5">从暂存缓冲区到暂存图像</a></li>
<li class="toctree-l2"><a class="reference internal" href="#blit">从暂存图像blit到交换链图像</a></li>
</ul>
</li>
<li class="toctree-l1"><a class="reference internal" href="Ch7-7%20%E4%BD%BF%E7%94%A8%E8%B4%B4%E5%9B%BE.html">Ch7-7 使用贴图</a></li>
</ul>
<p class="caption" role="heading"><span class="caption-text">第八章 简单示例</span></p>
<ul>
<li class="toctree-l1"><a class="reference internal" href="Ch8-1%20%E7%A6%BB%E5%B1%8F%E6%B8%B2%E6%9F%93.html">Ch8-1 离屏渲染</a></li>
<li class="toctree-l1"><a class="reference internal" href="Ch8-2%20%E6%B7%B1%E5%BA%A6%E6%B5%8B%E8%AF%95%E5%92%8C%E6%B7%B1%E5%BA%A6%E5%8F%AF%E8%A7%86%E5%8C%96.html">Ch8-2 深度测试和深度可视化</a></li>
<li class="toctree-l1"><a class="reference internal" href="Ch8-3%20%E5%BB%B6%E8%BF%9F%E6%B8%B2%E6%9F%93.html">Ch8-3 延迟渲染</a></li>
<li class="toctree-l1"><a class="reference internal" href="Ch8-4%20%E9%A2%84%E4%B9%98Alpha.html">Ch8-4 预乘Alpha</a></li>
<li class="toctree-l1"><a class="reference internal" href="Ch8-5%20sRGB%E8%89%B2%E5%BD%A9%E7%A9%BA%E9%97%B4%E4%B8%8E%E5%BC%80%E5%90%AFHDR.html">Ch8-5 sRGB色彩空间与开启HDR</a></li>
</ul>
<p class="caption" role="heading"><span class="caption-text">附录</span></p>
<ul>
<li class="toctree-l1"><a class="reference internal" href="Ap1-1%20%E8%BF%90%E8%A1%8C%E6%9C%9F%E7%BC%96%E8%AF%91GLSL.html">Ap1-1 运行期编译GLSL</a></li>
</ul>

        </div>
      </div>
    </nav>

    <section data-toggle="wy-nav-shift" class="wy-nav-content-wrap"><nav class="wy-nav-top" aria-label="Mobile navigation menu" >
          <i data-toggle="wy-nav-top" class="fa fa-bars"></i>
          <a href="index.html">EasyVulkan</a>
      </nav>

      <div class="wy-nav-content">
        <div class="rst-content">
          <div role="navigation" aria-label="Page navigation">
  <ul class="wy-breadcrumbs">
      <li><a href="index.html" class="icon icon-home"></a> &raquo;</li>
      <li>Ch7-6 拷贝图像到屏幕</li>
      <li class="wy-breadcrumbs-aside">
      </li>
  </ul>
  <hr/>
</div>
          <div role="main" class="document" itemscope="itemscope" itemtype="http://schema.org/Article">
           <div itemprop="articleBody">
             
  <section id="ch7-6">
<h1>Ch7-6 拷贝图像到屏幕<a class="headerlink" href="#ch7-6" title="Permalink to this heading"></a></h1>
<p>
    本节的<span class="path">main.cpp</span>对应示例代码中的：<a href="https://github.com/EasyVulkan/EasyVulkan.github.io/tree/main/solution/EasyVulkan_Ch7/Ch7-6.hpp">Ch7-6.hpp</a>
</p>
<p>
    由于到成功渲染出图像为止有诸多步骤，本套教程中分两步走。
    <br>
    本节学习如何使用stb_image读取图像数据、如何将图像数据拷贝到设备内存，以及blit命令。
    <br>
    阅读本节前请确保已阅读<a class="reference internal" href="Ch3-2%20%E5%9B%BE%E5%83%8F%E4%B8%8E%E7%BC%93%E5%86%B2%E5%8C%BA.html#image">Ch3-2中关于image的说明</a>。
</p><section id="id1">
<h2>将图像拷贝到交换链图像的流程<a class="headerlink" href="#id1" title="Permalink to this heading"></a></h2>
<p>
    将图像数据直接拷贝或blit到交换链图像，是通过Vulkan在屏幕上显示内容的最快方式。
    <br>
    当你的程序有大量的需要加载或创建的资源时，你可以通过这一方法来显示所谓的“启动画面”，展示一张诸如你的LOGO之类的图像。
</p>
<p>
    以下是将图像数据拷贝到交换链图像的流程：
    <br>
    1.读取图像数据到CPU内存
    <br>
    2.将图像数据从CPU内存拷贝到暂存缓冲区
    <br>
    <strong>接下来，根据图像尺寸与交换链图像是否相同，以及图像格式是否相同，有两个分支：</strong>
    <br>
    （A：若图像与交换链图像尺寸、格式相同）
    <br>
    A-3.将图像数据从暂存缓冲区直接拷贝到交换链图像
    <br>
    （B：若图像与交换链图像尺寸、格式不同）
    <br>
    B-3.获取与暂存缓冲区混叠的暂存图像，若无法混叠，将图像数据从暂存缓冲区直接拷贝到一张新建的暂存图像
    <br>
    B-4.将图像数据从暂存图像blit到交换链图像
</p></section>
<section id="id2">
<h2>读取图像数据<a class="headerlink" href="#id2" title="Permalink to this heading"></a></h2>
<p>
    考虑读取图像的两种情形：
    <br>
    1.直接从硬盘读取文件
    <br>
    2.从内存读取文件数据
    <br>
    如果你想把所有东西（图像、音频等会在你的程序里用到的资源）都打包进单个可执行程序，或把资源扔进dll，那么得先用平台特定的函数从程序自身或dll读取资源，获得资源的内存地址。然后再从内存读取并解析文件数据。
</p>
<p>
    为了省去重复的代码，我打算将两种情形统一到同一个模板函数中，然后根据参数类型应用编译期分支。
    <br>
    在<span class="path">VKBase+.h</span>中，vulkan命名空间中加入类<span class="type">texture</span>，并定义受保护静态成员函数<span class="sfn">LoadFile_Internal</span>(...)：
</p>
<pre class="code">
<span class="kw">class</span> <span class="type">texture</span> {
<span class="kw">protected</span>:
    <span class="kw">static</span> std::<span class="type">unique_ptr</span>&lt;<span class="type">uint8_t</span>[]&gt; <span class="sfn">LoadFile_Internal</span>(
        <span class="kw">const auto</span>* address,             <span class="cmt">//字符串指针，或资源的内存地址</span>
        <span class="type">size_t</span> fileSize,                 <span class="cmt">//文件大小，当且仅当传入的是资源的内存地址时有需要</span>
        <span class="type">VkExtent2D</span>&amp; extent,              <span class="cmt">//图像大小，将由stb_image的函数写入</span>
        <span class="type">formatInfo</span> requiredFormatInfo) { <span class="cmt">//对读取所得数据的格式要求</span>
<span class="pragma">#ifndef</span> <span class="mcr">NDEBUG</span>
        <span class="cmt">/*待填充*/</span>
<span class="pragma">#endif</span>
        <span class="kw">void</span>* pImageData = <span class="kw">nullptr</span>;<span class="cmt">//用于接收读取到的图像数据</span>
        <span class="cmt">//编译期分支：若传入的address是文件路径（字符串）</span>
        <span class="kw">if constexpr</span> (std::<span class="cncpt">same_as</span>&lt;<span class="kw">decltype</span>(address), <span class="kw">const char</span>*&gt;) {
            <span class="cmt">/*待填充*/</span>
        }
        <span class="cmt">//编译期分支：若传入的address是内存地址</span>
        <span class="kw">if constexpr</span> (std::<span class="cncpt">same_as</span>&lt;<span class="kw">decltype</span>(address), <span class="kw">const</span> <span class="type">uint8_t</span>*&gt;) {
            <span class="cmt">/*待填充*/</span>
        }
        <span class="kw">return</span> std::<span class="type">unique_ptr</span>&lt;<span class="type">uint8_t</span>[]&gt;(<span class="kw">static_cast</span>&lt;<span class="type">uint8_t</span>*&gt;(pImageData));
    }
};
</pre>
<p>
    除了<span class="var">pImageData</span>外，stb_image的读图函数还会要求以下参数：
</p>
<pre class="code">
<span class="cmt">//用于接收长和宽，extent.width和extent.height是uint32_t，因此需要转换</span>
<span class="kw">int</span>&amp; width = <span class="kw">reinterpret_cast</span>&lt;<span class="kw">int</span>&amp;&gt;(extent.width);
<span class="kw">int</span>&amp; height = <span class="kw">reinterpret_cast</span>&lt;<span class="kw">int</span>&amp;&gt;(extent.height);
<span class="cmt">//用于接收原始色彩通道数，读完图后我用不着它，所以也没有将其初始化</span>
<span class="kw">int</span> channelCount;
</pre>
<p>
    有些格式要求是无法满足的，因此加入在Debug Build下检查<span class="var">requiredFormatInfo</span>的代码：
</p>
<pre class="code">
<span class="pragma">#ifndef</span> <span class="mcr">NDEBUG</span>
<span class="kw">if</span> (<span class="cmt">//若要求数据为浮点数，stb_image只支持32位浮点数</span>
    (requiredFormatInfo.rawDataType == formatInfo::<span class="type">floatingPoint</span> &amp;&amp; requiredFormatInfo.sizePerComponent == 4) ||
    <span class="cmt">//若要求数据为整形，stb_image只支持8位或16位每通道</span>
    (requiredFormatInfo.rawDataType == formatInfo::<span class="type">integer</span> &amp;&amp; requiredFormatInfo.sizePerComponent &gt;= 1 &amp;&amp; requiredFormatInfo.sizePerComponent &lt;= 2))
    <span class="cmt">/*空表达式*/</span>;
<span class="kw">else</span>
    outStream &lt;&lt; std::<span class="fn">format</span>(<span class="str">&quot;[ texture ] ERROR\nRequired format is not available for source image data!\n&quot;</span>),
    <span class="fn">abort</span>();
<span class="pragma">#endif</span>
</pre>
<p>
    做掉<span class="kw">else</span>分支，则目前为止<span class="sfn">LoadFile_Internal</span>(...)为：
</p>
<pre class="code">
<span class="kw">static</span> std::<span class="type">unique_ptr</span>&lt;<span class="type">uint8_t</span>[]&gt; <span class="sfn">LoadFile_Internal</span>(<span class="kw">const auto</span>* address, <span class="type">size_t</span> fileSize, <span class="type">VkExtent2D</span>&amp; extent, <span class="type">formatInfo</span> requiredFormatInfo) {
<span class="pragma">#ifndef</span> <span class="mcr">NDEBUG</span>
    <span class="kw">if</span> (!(requiredFormatInfo.rawDataType == formatInfo::<span class="type">floatingPoint</span> &amp;&amp; requiredFormatInfo.sizePerComponent == 4) &amp;&amp;
        !(requiredFormatInfo.rawDataType == formatInfo::<span class="type">integer</span> &amp;&amp; requiredFormatInfo.sizePerComponent &amp;&amp; requiredFormatInfo.sizePerComponent &lt;= 2))
        outStream &lt;&lt; std::<span class="fn">format</span>(<span class="str">&quot;[ texture ] ERROR\nRequired format is not available for source image data!\n&quot;</span>),
        <span class="fn">abort</span>();
<span class="pragma">#endif</span>
    <span class="kw">int</span>&amp; width = <span class="kw">reinterpret_cast</span>&lt;<span class="kw">int</span>&amp;&gt;(extent.width);
    <span class="kw">int</span>&amp; height = <span class="kw">reinterpret_cast</span>&lt;<span class="kw">int</span>&amp;&gt;(extent.height);
    <span class="kw">int</span> channelCount;
    <span class="kw">void</span>* pImageData = <span class="kw">nullptr</span>;
    <span class="kw">if constexpr</span> (std::<span class="cncpt">same_as</span>&lt;<span class="kw">decltype</span>(address), <span class="kw">const char</span>*&gt;) {
        <span class="cmt">/*待填充*/</span>
    }
    <span class="kw">if constexpr</span> (std::<span class="cncpt">same_as</span>&lt;<span class="kw">decltype</span>(address), <span class="kw">const</span> <span class="type">uint8_t</span>*&gt;) {
        <span class="cmt">/*待填充*/</span>
    }
    <span class="kw">return</span> std::<span class="type">unique_ptr</span>&lt;<span class="type">uint8_t</span>[]&gt;(<span class="kw">static_cast</span>&lt;<span class="type">uint8_t</span>*&gt;(pImageData));
}
</pre>
<ul>
    <li>
        <p>
            std::<span class="cncpt">same_as</span>是一个C++概念（<span class="kw">concept</span>），根据运行期的类型判断被转为相应布尔值，若类型一致则为真。若对C++概念不熟悉，请自行参阅相关文档。
        </p>
    </li>
</ul>
<p>
    用<span class="fn">stbi_load</span>(...)将图像文件读取为8位整形序列：
</p>
<table class="docutils align-default">
    <colgroup>
        <col style="width: 30%">
        <col style="width: 70%">
    </colgroup>
    <thead>
        <tr class="row-odd">
            <th class="head" colspan="2"><p><span class="mcr">STBIDEF</span> <span class="type">stbi_uc</span>* <span class="fn">stbi_load</span>(...) 的参数说明</p></th>
        </tr>
    </thead>
    <tbody>
        <tr class="row-even">
            <td><p><span class="kw">const char</span>* filename</p></td>
            <td><p>文件路径</p></td>
        </tr>
        <tr class="row-odd">
            <td><p><span class="kw">int</span>* x</p></td>
            <td><p>输出图像宽到*x</p></td>
        </tr>
        <tr class="row-even">
            <td><p><span class="kw">int</span>* y</p></td>
            <td><p>输出图像宽到*y</p></td>
        </tr>
        <tr class="row-odd">
            <td><p><span class="kw">int</span>* comp</p></td>
            <td><p>输出图像色彩通道数到*comp</p></td>
        </tr>
        <tr class="row-even">
            <td><p><span class="kw">int</span> req_comp</p></td>
            <td><p>若非0，图像色彩通道数会被减少或补齐到req_comp</p></td>
        </tr>
    </tbody>
</table>
<ul>
    <li>
        <p>
            该函数自动识别文件类型，支持bmp、tga、png、jpeg、hdr等常见格式（具体请自行参见文件内注释）。如果你事先确定文件类型，也可以直接用诸如<span class="fn">stbi__tga_load</span>(...)之类具体格式的读取函数。
        </p>
    </li>
    <li>
        <p>
            若函数执行成功，返回读取所得数据的内存地址，否则为<span class="kw">nullptr</span>。
        </p>
    </li>
</ul>
<p>
    类似地，用<span class="fn">stbi_load_16</span>(...)将图像读取为16位整形序列，用<span class="fn">stbi_loadf</span>(...)将图像读取为32位浮点数序列，参数与<span class="fn">stbi_load</span>(...)一致。
    <br>
    于是，根据<span class="var">requiredFormatInfo</span>中的各种要求，有以下分支：
</p>
<pre class="code">
<span class="kw">if</span> (requiredFormatInfo.rawDataType == formatInfo::<span class="enum">integer</span>)
    <span class="kw">if</span> (requiredFormatInfo.sizePerComponent == 1)
        pImageData = <span class="fn">stbi_load</span>(address, &amp;width, &amp;height, &amp;channelCount, requiredFormatInfo.componentCount);
    <span class="kw">else</span>
        pImageData = <span class="fn">stbi_load_16</span>(address, &amp;width, &amp;height, &amp;channelCount, requiredFormatInfo.componentCount);
<span class="kw">else</span>
    pImageData = <span class="fn">stbi_loadf</span>(address, &amp;width, &amp;height, &amp;channelCount, requiredFormatInfo.componentCount);
<span class="kw">if</span> (!pImageData)
    outStream &lt;&lt; std::<span class="fn">format</span>(<span class="str">&quot;[ texture ] ERROR\nFailed to load the file: {}\n&quot;</span>, address);
</pre>
<p>
    从内存读取文件数据时，步骤几乎一致，差别仅在于：要提供文件数据的大小，以及各个读取函数替换成对应的有_from_memory后缀的版本：
</p>
<table class="docutils align-default">
    <colgroup>
        <col style="width: 30%">
        <col style="width: 70%">
    </colgroup>
    <thead>
        <tr class="row-odd">
            <th class="head" colspan="2"><p><span class="mcr">STBIDEF</span> <span class="type">stbi_uc</span>* <span class="fn">stbi_load_from_memory</span>(...) 的参数说明</p></th>
        </tr>
    </thead>
    <tbody>
        <tr class="row-even">
            <td><p><span class="kw">const</span> <span class="type">stbi_uc</span>* buffer</p></td>
            <td><p>文件数据的内存地址</p></td>
        </tr>
        <tr class="row-odd">
            <td><p><span class="kw">int</span> len</p></td>
            <td><p>文件数据的大小</p></td>
        </tr>
        <tr class="row-even">
            <td><p><span class="kw">int</span>* x</p></td>
            <td><p>输出图像宽到*x</p></td>
        </tr>
        <tr class="row-odd">
            <td><p><span class="kw">int</span>* y</p></td>
            <td><p>输出图像宽到*y</p></td>
        </tr>
        <tr class="row-even">
            <td><p><span class="kw">int</span>* comp</p></td>
            <td><p>输出图像色彩通道数到*comp</p></td>
        </tr>
        <tr class="row-odd">
            <td><p><span class="kw">int</span> req_comp</p></td>
            <td><p>若非0，图像色彩通道数会被减少或补齐到req_comp</p></td>
        </tr>
    </tbody>
</table>
<p>
    既然len的类型是<span class="kw">int</span>，那么显然，文件数据不能达到2G。
    <br>
    最后整个<span class="sfn">LoadFile_Internal</span>(...)如下：
</p>
<pre class="code">
<span class="kw">static</span> std::<span class="type">unique_ptr</span>&lt;<span class="type">uint8_t</span>[]&gt; <span class="sfn">LoadFile_Internal</span>(<span class="kw">const auto</span>* address, <span class="type">size_t</span> fileSize, <span class="type">VkExtent2D</span>&amp; extent, <span class="type">formatInfo</span> requiredFormatInfo) {
<span class="pragma">#ifndef</span> <span class="mcr">NDEBUG</span>
    <span class="kw">if</span> (!(requiredFormatInfo.rawDataType == formatInfo::<span class="type">floatingPoint</span> &amp;&amp; requiredFormatInfo.sizePerComponent == 4) &amp;&amp;
        !(requiredFormatInfo.rawDataType == formatInfo::<span class="type">integer</span> &amp;&amp; <span class="fn">Between_Closed</span>&lt;<span class="type">int32_t</span>&gt;(1, requiredFormatInfo.sizePerComponent, 2)))
        outStream &lt;&lt; std::<span class="fn">format</span>(<span class="str">&quot;[ texture ] ERROR\nRequired format is not available for source image data!\n&quot;</span>),
        <span class="fn">abort</span>();
<span class="pragma">#endif</span>
    <span class="kw">int</span>&amp; width = <span class="kw">reinterpret_cast</span>&lt;<span class="kw">int</span>&amp;&gt;(extent.width);
    <span class="kw">int</span>&amp; height = <span class="kw">reinterpret_cast</span>&lt;<span class="kw">int</span>&amp;&gt;(extent.height);
    <span class="kw">int</span> channelCount;
    <span class="kw">void</span>* pImageData = <span class="kw">nullptr</span>;
    <span class="kw">if constexpr</span> (std::<span class="cncpt">same_as</span>&lt;<span class="kw">decltype</span>(address), <span class="kw">const char</span>*&gt;) {
        <span class="kw">if</span> (requiredFormatInfo.rawDataType == formatInfo::<span class="enum">integer</span>)
            <span class="kw">if</span> (requiredFormatInfo.sizePerComponent == 1)
                pImageData = <span class="fn">stbi_load</span>(address, &amp;width, &amp;height, &amp;channelCount, requiredFormatInfo.componentCount);
            <span class="kw">else</span>
                pImageData = <span class="fn">stbi_load_16</span>(address, &amp;width, &amp;height, &amp;channelCount, requiredFormatInfo.componentCount);
        <span class="kw">else</span>
            pImageData = <span class="fn">stbi_loadf</span>(address, &amp;width, &amp;height, &amp;channelCount, requiredFormatInfo.componentCount);
        <span class="kw">if</span> (!pImageData)
            outStream &lt;&lt; std::<span class="fn">format</span>(<span class="str">&quot;[ texture ] ERROR\nFailed to load the file: {}\n&quot;</span>, address);
    }
    <span class="kw">if constexpr</span> (std::<span class="cncpt">same_as</span>&lt;<span class="kw">decltype</span>(address), <span class="kw">const</span> <span class="type">uint8_t</span>*&gt;) {
        <span class="kw">if</span> (fileSize &gt; <span class="mcr">INT32_MAX</span>) {
            outStream &lt;&lt; std::<span class="fn">format</span>(<span class="str">&quot;[ texture ] ERROR\nFailed to load image data from the given address! Data size must be less than 2G!\n&quot;</span>);
            <span class="kw">return</span> {};
        }
        <span class="kw">if</span> (requiredFormatInfo.rawDataType == formatInfo::<span class="enum">integer</span>)
            <span class="kw">if</span> (requiredFormatInfo.sizePerComponent == 1)
                pImageData = <span class="fn">stbi_load_from_memory</span>(address, fileSize, &amp;width, &amp;height, &amp;channelCount, requiredFormatInfo.componentCount);
            <span class="kw">else</span>
                pImageData = <span class="fn">stbi_load_16_from_memory</span>(address, fileSize, &amp;width, &amp;height, &amp;channelCount, requiredFormatInfo.componentCount);
        <span class="kw">else</span>
            pImageData = <span class="fn">stbi_loadf_from_memory</span>(address, fileSize, &amp;width, &amp;height, &amp;channelCount, requiredFormatInfo.componentCount);
        <span class="kw">if</span> (!pImageData)
            outStream &lt;&lt; std::<span class="fn">format</span>(<span class="str">&quot;[ texture ] ERROR\nFailed to load image data from the given address!\n&quot;</span>);
    }
    <span class="kw">return</span> std::<span class="type">unique_ptr</span>&lt;<span class="type">uint8_t</span>[]&gt;(<span class="kw">static_cast</span>&lt;<span class="type">uint8_t</span>*&gt;(pImageData));
}
</pre>
<ul>
    <li>
        <p>
            这里为了让代码不太难看，使用了<span class="fn">Between_Closed</span>(...)来替代先前相同的代码逻辑，该函数见示例代码中的<a href="https://github.com/EasyVulkan/EasyVulkan.github.io/blob/main/solution/EasyVulkan_Ch7/EasyVKStart.h#L71">EasyVKStart.h</a>。
        </p>
    </li>
</ul>
<p>
    如果你需要根据图片的通道数<span class="var">channelCount</span>及你指定的其他参数确定对应的<span class="type">VkFormat</span>，自行修改函数。会有些麻烦，需要写一张映射表。
</p>
<p>
    注意到我给上述函数起的名字有_Internal后缀，你可能已经意识到了我接下来要干什么：
</p>
<pre class="code">
<span class="kw">class</span> <span class="type">texture</span> {
<span class="kw">protected</span>:
    <span class="kw">static</span> std::<span class="type">unique_ptr</span>&lt;<span class="type">uint8_t</span>[]&gt; <span class="sfn">LoadFile_Internal</span>(<span class="kw">const auto</span>* address, <span class="type">size_t</span> fileSize, <span class="type">VkExtent2D</span>&amp; extent, <span class="type">formatInfo</span> requiredFormatInfo) {
        <span class="cmt">/*...*/</span>
    }
<span class="kw">public</span>:
    [[nodiscard]]
    <span class="kw">static</span> std::<span class="type">unique_ptr</span>&lt;<span class="type">uint8_t</span>[]&gt; <span class="sfn">LoadFile</span>(<span class="kw">const char</span>* filepath, <span class="type">VkExtent2D</span>&amp; extent, <span class="type">formatInfo</span> requiredFormatInfo) {
        <span class="kw">return</span> <span class="sfn">LoadFile_Internal</span>(filepath, 0, extent, requiredFormatInfo);
    }
    [[nodiscard]]
    <span class="kw">static</span> std::<span class="type">unique_ptr</span>&lt;<span class="type">uint8_t</span>[]&gt; <span class="sfn">LoadFile</span>(<span class="kw">const</span> <span class="type">uint8_t</span>* fileBinaries, <span class="type">size_t</span> fileSize, <span class="type">VkExtent2D</span>&amp; extent, <span class="type">formatInfo</span> requiredFormatInfo) {
        <span class="kw">return</span> <span class="sfn">LoadFile_Internal</span>(fileBinaries, fileSize, extent, requiredFormatInfo);
    }
};
</pre>
<p>
    顺便，提供在Windows上从exe或dll读取资源的函数。
    <br>
    关于以下代码具体的含义，以及Visual Studio中如何打包资源，因与Vulkan无关，请自行搜索相关教程。
</p>
<pre class="code">
std::<span class="type">pair</span>&lt;<span class="kw">const</span> <span class="type">uint8_t</span>*, <span class="type">size_t</span>&gt; <span class="fn">LoadResourceFromModule</span>(<span class="type">int32_t</span> resourceId, <span class="type">HMODULE</span> hModule = <span class="mcr">NULL</span>) {
    <span class="kw">if</span> (<span class="type">HRSRC</span> hResource = <span class="fn">FindResource</span>(hModule, <span class="mcr">MAKEINTRESOURCE</span>(resourceId), <span class="mcr">RT_RCDATA</span>))
        <span class="kw">if</span> (<span class="type">HGLOBAL</span> hData = <span class="fn">LoadResource</span>(hModule, hResource))
            <span class="kw">if</span> (<span class="kw">const</span> <span class="type">uint8_t</span>* pData = <span class="kw">static_cast</span>&lt;<span class="type">uint8_t</span>*&gt;(<span class="fn">LockResource</span>(hData)))
                <span class="kw">return</span> { pData, <span class="fn">SizeofResource</span>(hModule, hResource) };
    <span class="kw">return</span> {};
}
</pre></section>
<section id="bootscreen">
<h2>BootScreen<a class="headerlink" href="#bootscreen" title="Permalink to this heading"></a></h2>
<p>
    我们得有一个函数来放这一节接下来要写的东西。
    <br>
    于是乎，既然本节的目标就只是拷贝图像到屏幕，那么如我前面所说的“启动画面”这一非常简单的应用场景就非常合适。
    <br>
    在<span class="path">EasyVulkan.hpp</span>，easyVulkan命名空间中加入：
</p>
<pre class="code">
<span class="kw">void</span> <span class="fn">BootScreen</span>(<span class="kw">const char</span>* imagePath, <span class="type">VkFormat</span> imageFormat) {
    <span class="type">VkExtent2D</span> imageExtent;
    std::<span class="type">unique_ptr</span>&lt;<span class="type">uint8_t</span>[]&gt; pImageData = <span class="type">texture2d</span>::<span class="sfn">LoadFile</span>(imagePath, imageExtent, <span class="fn">FormatInfo</span>(imageFormat));
    <span class="kw">if</span> (!pImageData)
        <span class="kw">return</span>;<span class="cmt">//这里我就偷个懒，不写错误信息了</span>
    <span class="cmt">/*待填充*/</span>
}
</pre>
<p>
    接着先来学习下前面学过的东西。
    <br>
    于是我现在不需要构建渲染循环，只需要提交一次命令缓冲区就够了，不用考虑之后的事，且命令缓冲区中不需要渲染通道。
    <br>
    那么需要哪些对象，并调用哪些函数呢？如果你尚不能信手拈来地写一个渲染循环，那么我建议你在此先回忆一下。
    <br>
    ...
    <br>
    显然需要命令缓冲区，因为已经直接写在了题设里。
    <br>
    ...
    <br>
    录制、提交并等待命令缓冲区需要配一个栅栏。
    <br>
    ...
    <br>
    既然要用到交换链图像，那么肯定得先获得它。在我的封装中，这一步用<span class="type">graphicsBase</span>::<span class="fn">SwapImage</span>(...)进行。
    <br>
    ...
    <br>
    于是，函数加入如下内容：
</p>
<pre class="code">
<span class="kw">void</span> <span class="fn">BootScreen</span>(<span class="kw">const char</span>* imagePath, <span class="type">VkFormat</span> imageFormat) {
    <span class="cmt">//用先前写的函数从硬盘读图</span>
    <span class="type">VkExtent2D</span> imageExtent;
    std::<span class="type">unique_ptr</span>&lt;<span class="type">uint8_t</span>[]&gt; pImageData = <span class="type">texture2d</span>::<span class="sfn">LoadFile</span>(imagePath, imageExtent, <span class="fn">FormatInfo</span>(imageFormat));
    <span class="kw">if</span> (!pImageData)
        <span class="kw">return</span>;<span class="cmt">//这里我就偷个懒，不写错误信息了</span>
    <span class="cmt">/*待填充*/</span>

    <span class="cmt">//创建同步对象</span>
    <span class="type">semaphore</span> semaphore_imageIsAvailable;
    <span class="type">fence</span> fence;
    <span class="cmt">//分配命令缓冲区</span>
    <span class="type">commandBuffer</span> commandBuffer;
    <span class="type">graphicsBase</span>::<span class="sfn">Plus</span>().<span class="fn">CommandPool_Graphics</span>().<span class="fn">AllocateBuffers</span>(commandBuffer);

    <span class="cmt">//获取交换链图像</span>
    <span class="type">graphicsBase</span>::<span class="sfn">Base</span>().<span class="fn">SwapImage</span>(semaphore_imageIsAvailable);

    <span class="cmt">//录制命令缓冲区</span>
    commandBuffer.<span class="fn">Begin</span>(<span class="enum">VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT</span>);
    <span class="cmt">/*待填充*/</span>
    commandBuffer.<span class="fn">End</span>();

    <span class="cmt">//提交命令缓冲区</span>
    <span class="type">VkPipelineStageFlags</span> waitDstStage = <span class="enum">VK_PIPELINE_STAGE_TRANSFER_BIT</span>;
    <span class="type">VkSubmitInfo</span> submitInfo = {
        .waitSemaphoreCount = 1,
        .pWaitSemaphores = semaphore_imageIsAvailable.<span class="fn">Address</span>(),
        .pWaitDstStageMask = &amp;waitDstStage,
        .commandBufferCount = 1,
        .pCommandBuffers = commandBuffer.<span class="fn">Address</span>()
    };
    <span class="type">graphicsBase</span>::<span class="sfn">Base</span>().<span class="fn">SubmitCommandBuffer_Graphics</span>(submitInfo, fence);
    <span class="cmt">//等待命令执行完毕</span>
    fence.<span class="fn">WaitAndReset</span>();
    <span class="cmt">//呈现图像</span>
    <span class="type">graphicsBase</span>::<span class="sfn">Base</span>().<span class="fn">PresentImage</span>();

    <span class="cmt">//别忘了释放命令缓冲区</span>
    <span class="type">graphicsBase</span>::<span class="sfn">Plus</span>().<span class="fn">CommandPool_Graphics</span>().<span class="fn">FreeBuffers</span>(commandBuffer);
}
</pre>
<ul>
    <li>
        <p>
            因为之后会使用的拷贝和Blit命令都是传输命令，所以<span class="var">waitDstStage</span>是<span class="enum">VK_PIPELINE_STAGE_TRANSFER_BIT</span>。
        </p>
    </li>
    <li>
        <p>
            因为这里的应用场景不要求同步粒度，因此我直接在呈现图像前等待<span class="var">fence</span>，而不必使用第二个信号量。
        </p>
    </li>
</ul>
<p>
    然后我们将内存中的图像数据存入暂存缓冲区：
</p>
<pre class="code">
<span class="kw">void</span> <span class="fn">BootScreen</span>(<span class="kw">const char</span>* imagePath, <span class="type">VkFormat</span> imageFormat) {
    <span class="type">VkExtent2D</span> imageExtent;
    std::<span class="type">unique_ptr</span>&lt;<span class="type">uint8_t</span>[]&gt; pImageData = <span class="type">texture2d</span>::<span class="sfn">LoadFile</span>(imagePath, imageExtent, <span class="fn">FormatInfo</span>(imageFormat));
    <span class="kw">if</span> (!pImageData)
        <span class="kw">return</span>;
    <span class="cmt">/*新增*/</span><span class="type">stagingBuffer</span>::<span class="sfn">BufferData_MainThread</span>(pImageData.<span class="fn">get</span>(), <span class="fn">FormatInfo</span>(imageFormat).sizePerPixel * imageExtent.width * imageExtent.height);

    <span class="type">semaphore</span> semaphore_imageIsAvailable;
    <span class="type">fence</span> fence;
    <span class="type">commandBuffer</span> commandBuffer;
    <span class="type">graphicsBase</span>::<span class="sfn">Plus</span>().<span class="fn">CommandPool_Graphics</span>().<span class="fn">AllocateBuffers</span>(commandBuffer);

    <span class="type">graphicsBase</span>::<span class="sfn">Base</span>().<span class="fn">SwapImage</span>(semaphore_imageIsAvailable);

    commandBuffer.<span class="fn">Begin</span>(<span class="enum">VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT</span>);
    <span class="cmt">/*待填充*/</span>
    commandBuffer.<span class="fn">End</span>();

    <span class="type">VkPipelineStageFlags</span> waitDstStage = <span class="enum">VK_PIPELINE_STAGE_TRANSFER_BIT</span>;
    <span class="type">VkSubmitInfo</span> submitInfo = {
        .waitSemaphoreCount = 1,
        .pWaitSemaphores = semaphore_imageIsAvailable.<span class="fn">Address</span>(),
        .pWaitDstStageMask = &amp;waitDstStage,
        .commandBufferCount = 1,
        .pCommandBuffers = commandBuffer.<span class="fn">Address</span>()
    };
    <span class="type">graphicsBase</span>::<span class="sfn">Base</span>().<span class="fn">SubmitCommandBuffer_Graphics</span>(submitInfo, fence);
    fence.<span class="fn">WaitAndReset</span>();
    <span class="type">graphicsBase</span>::<span class="sfn">Base</span>().<span class="fn">PresentImage</span>();

    <span class="type">graphicsBase</span>::<span class="sfn">Plus</span>().<span class="fn">CommandPool_Graphics</span>().<span class="fn">FreeBuffers</span>(commandBuffer);
}
</pre>
<p>
    接着就是确定分支了：能否从暂存缓冲区直接拷贝到交换链图像，还是说需要blit？
</p>
<pre class="code">
commandBuffer.<span class="fn">Begin</span>(<span class="enum">VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT</span>);

<span class="type">VkExtent2D</span> swapchainImageSize = <span class="type">graphicsBase</span>::<span class="sfn">Base</span>().<span class="fn">SwapchainCreateInfo</span>().imageExtent;
<span class="kw">bool</span> blit =
    imageExtent.width != swapchainImageSize.width ||                       <span class="cmt">//宽</span>
    imageExtent.height != swapchainImageSize.height ||                     <span class="cmt">//高</span>
    imageFormat != <span class="type">graphicsBase</span>::<span class="sfn">Base</span>().<span class="fn">SwapchainCreateInfo</span>().imageFormat; <span class="cmt">//图像格式</span>

    <span class="cmt">/*这里还缺点东西，待填充*/</span>
    <span class="kw">if</span> (blit) {
        <span class="cmt">/*需要blit的分支，待填充*/</span>
    }
    <span class="kw">else</span> {
        <span class="cmt">/*直接copy的分支，待填充*/</span>
    }

commandBuffer.<span class="fn">End</span>();
</pre></section>
<section id="id3">
<h2>图像操作<a class="headerlink" href="#id3" title="Permalink to this heading"></a></h2>
<p>
    在<span class="path">VKBase+.h</span>中，vulkan命名空间中加入结构体或类<span class="type">imageOperation</span>。这个类型没有成员也不会创建实例，相比在命名空间中书写全局函数，非常理所当然地，我更倾向于在类中写静态成员函数。
    <br>
    然后定义一个<span class="type">imageOperation</span>的内部类型<span class="type">imageMemoryBarrierParameterPack</span>和两个静态成员函数：
</p>
<pre class="code">
<span class="kw">struct</span> <span class="type">imageOperation</span> {
    <span class="kw">struct</span> <span class="type">imageMemoryBarrierParameterPack</span> {
        <span class="cmt">/*待填充*/</span>
    };
    <span class="kw">static void</span> <span class="sfn">CmdCopyBufferToImage</span>(<span class="type">VkCommandBuffer</span> commandBuffer, <span class="type">VkBuffer</span> buffer, <span class="type">VkImage</span> image, <span class="kw">const</span> <span class="type">VkBufferImageCopy</span>&amp; region,
        <span class="type">imageMemoryBarrierParameterPack</span> imb_from, <span class="type">imageMemoryBarrierParameterPack</span> imb_to) {
        <span class="cmt">/*待填充*/</span>
    }
    <span class="kw">static void</span> <span class="sfn">CmdBlitImage</span>(<span class="type">VkCommandBuffer</span> commandBuffer, <span class="type">VkImage</span> image_src, <span class="type">VkImage</span> image_dst, <span class="kw">const</span> <span class="type">VkImageBlit</span>&amp; region,
        <span class="type">imageMemoryBarrierParameterPack</span> imb_dst_from, <span class="type">imageMemoryBarrierParameterPack</span> imb_dst_to, <span class="type">VkFilter</span> filter = <span class="mcr">VK_FILTER_LINEAR</span>) {
        <span class="cmt">/*待填充*/</span>
    }
};
</pre>
<p>
    顾名思义，<span class="type">imageOperation</span>是图像操作相关的类，除了本节中用于从缓冲区将数据拷贝到图像的<span class="sfn">CmdCopyBufferToImage</span>(...)，以及将数据从图像blit到另一图像的<span class="sfn">CmdBlitImage</span>(...)外，之后还会有生成mipmap的函数，这些函数都将会非常实用。
    <br>
    其中<span class="type">imageMemoryBarrierParameterPack</span>是用于指定<a class="reference internal" href="Ch3-1%20%E5%90%8C%E6%AD%A5%E5%8E%9F%E8%AF%AD.html#pipeline-barrier">管线屏障及图像内存屏障</a>的参数集合，其定义如下：
</p>
<pre class="code">
<span class="kw">struct</span> <span class="type">imageMemoryBarrierParameterPack</span> {
    <span class="kw">const</span> <span class="kw">bool</span> isNeeded = <span class="kw">false</span>;                            <span class="cmt">//是否需要屏障，默认为false</span>
    <span class="kw">const</span> <span class="type">VkPipelineStageFlags</span> stage = 0;                   <span class="cmt">//srcStages或dstStages</span>
    <span class="kw">const</span> <span class="type">VkAccessFlags</span> access = 0;                         <span class="cmt">//srcAccessMask或dstAccessMask</span>
    <span class="kw">const</span> <span class="type">VkImageLayout</span> layout = <span class="mcr">VK_IMAGE_LAYOUT_UNDEFINED</span>; <span class="cmt">//oldLayout或newLayout</span>
    <span class="cmt">//默认构造器，isNeeded保留为false</span>
    <span class="kw">constexpr</span> <span class="fn">imageMemoryBarrierParameterPack</span>() = <span class="kw">default</span>;
    <span class="cmt">//若指定参数，三个参数必须被全部显示指定，isNeeded被赋值为true</span>
    <span class="kw">constexpr</span> <span class="fn">imageMemoryBarrierParameterPack</span>(<span class="type">VkPipelineStageFlags</span> stage, <span class="type">VkAccessFlags</span> access, <span class="type">VkImageLayout</span> layout) :
        isNeeded(<span class="kw">true</span>), stage(stage), access(access), layout(layout) {}
};
</pre>
<ul>
    <li>
        <p>
            指定构造器为<span class="kw">constexpr</span>以便后续创建可被<a href="https://en.cppreference.com/w/cpp/language/constant_initialization">常量初始化</a>的静态常量（涉及一些简化代码的技巧，会在<a class="reference internal" href="Ch5-2%202D%E8%B4%B4%E5%9B%BE%E5%8F%8A%E7%94%9F%E6%88%90Mipmap.html#blitandgeneratemipmap2d">Ch5-2</a>用到）。
        </p>
    </li>
</ul>
<p>
    在Vulkan 1.3中stage为0（<span class="enum">VK_PIPELINE_STAGE_NONE</span>）是有效值，<span class="mcr">VK_IMAGE_LAYOUT_UNDEFINED</span>的值是0，那么即便stage、access、layout三者皆为0，也未必构成无效参数。
    <br>
    因此不以三者是否全部为0来判断，而是用一个额外的成员isNeeded来标定是否需要管线屏障。如果不需要，则对传入<span class="sfn">CmdCopyBufferToImage</span>(...)和<span class="sfn">CmdBlitImage</span>(...)的相应参数使用<code>{}</code>默认初始化即可。
</p><section id="cmdcopybuffertoimage">
<h3>CmdCopyBufferToImage<a class="headerlink" href="#cmdcopybuffertoimage" title="Permalink to this heading"></a></h3>
<p>
    来简单看一下我为<span class="sfn">CmdCopyBufferToImage</span>规定的各个参数：
</p>
<pre class="code">
<span class="kw">static void</span> <span class="sfn">CmdCopyBufferToImage</span>(
    <span class="type">VkCommandBuffer</span> commandBuffer,   <span class="cmt">//命令缓冲区的handle</span>
    <span class="type">VkBuffer</span> buffer,                 <span class="cmt">//作为数据来源的缓冲区</span>
    <span class="type">VkImage</span> image,                   <span class="cmt">//接收数据的目标图像</span>
    <span class="kw">const</span> <span class="type">VkBufferImageCopy</span>&amp; region, <span class="cmt">//指定将缓冲区的哪些部分拷贝到图像的哪些部分</span>
    <span class="type">imageMemoryBarrierParameterPack</span> imb_from, <span class="cmt">//后述</span>
    <span class="type">imageMemoryBarrierParameterPack</span> imb_to) {
        <span class="cmt">/*待填充*/</span>
}
</pre>
<p>
    从一个<span class="type">VkBuffer</span>拷贝数据到一个<span class="type">VkImage</span>，通常需要考虑该操作前后是否需要管线屏障，因此我们初步将<span class="sfn">CmdCopyBufferToImage</span>变为：
</p>
<pre class="code">
<span class="kw">static void</span> <span class="sfn">CmdCopyBufferToImage</span>(<span class="type">VkCommandBuffer</span> commandBuffer, <span class="type">VkBuffer</span> buffer, <span class="type">VkImage</span> image, <span class="kw">const</span> <span class="type">VkBufferImageCopy</span>&amp; region,
    <span class="type">imageMemoryBarrierParameterPack</span> imb_from, <span class="type">imageMemoryBarrierParameterPack</span> imb_to) {
    <span class="cmt">//若拷贝前需要管线屏障</span>
    <span class="kw">if</span> (imb_from.isNeeded)
        <span class="cmt">/*待填充*/</span>
    <span class="cmt">//调用拷贝命令</span>
    <span class="fn">vkCmdCopyBufferToImage</span>(<span class="cmt">/*待填充*/</span>);
    <span class="cmt">//若拷贝后需要管线屏障</span>
    <span class="kw">if</span> (imb_to.isNeeded)
        <span class="cmt">/*待填充*/</span>
}
</pre>
<p>
    如果你还没有阅读本套教程中<a class="reference internal" href="Ch3-1%20%E5%90%8C%E6%AD%A5%E5%8E%9F%E8%AF%AD.html#pipeline-barrier">Pipeline Barrier</a>，那么是时候了。
</p>
<p>
    该解释<span class="var">imb_from</span>和<span class="var">imb_to</span>这两个参数的含义了，注意我用的后缀分别是_from和_to，“从”和“到”。
    <br>
    执行<span class="fn">vkCmdCopyBufferToImage</span>(...)命令时：
</p>
<ul>
    <li>
        <p>
            图像的内存布局应当被变成最适用于传输命令的<span class="enum">VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL</span>
        </p>
    </li>
    <li>
        <p>
            拷贝发生的管线阶段为<span class="enum">VK_PIPELINE_STAGE_TRANSFER_BIT</span>
        </p>
    </li>
    <li>
        <p>
            拷贝对应的操作类型为<span class="enum">VK_ACCESS_TRANSFER_WRITE_BIT</span>
        </p>
    </li>
</ul>
<p>
    于是，拷贝前的管线屏障旨在要求：先于该屏障的命令中由<span class="var">imb_from.stage</span>所指示的阶段（第一个同步域），必须在该屏障后的<span class="fn">vkCmdCopyBufferToImage</span>(...)命令执行到<span class="enum">VK_PIPELINE_STAGE_TRANSFER_BIT</span>阶段（第二个同步域）前完成，即拷贝发生前完成。
    <br>
    以防你没有仔细阅读<a class="reference internal" href="Ch3-1%20%E5%90%8C%E6%AD%A5%E5%8E%9F%E8%AF%AD.html#pipeline-barrier">管线屏障相关的说明</a>，再次强调，简而言之，管线屏障注明其前后的命令在执行时，有哪些阶段不能重叠。
    <br>
    图像的内存布局会在两个同步域间，由<span class="var">imb_from.layout</span>转到<span class="enum">VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL</span>。
    <br>
    此处的内存屏障还应当确保传输命令的写入操作（<span class="enum">VK_ACCESS_TRANSFER_WRITE_BIT</span>），在由<span class="var">imb_from.access</span>指明的、先前的写入操作的结果的基础上发生。
</p>
<p>
    于是拷贝前的管线屏障为：
</p>
<pre class="code">
<span class="cmt">//这个结构体不放if底下，后面改改还要用</span>
<span class="type">VkImageMemoryBarrier</span> imageMemoryBarrier = {
    <span class="enum">VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER</span>,
    <span class="kw">nullptr</span>,
    imb_from.access,
    <span class="enum">VK_ACCESS_TRANSFER_WRITE_BIT</span>,
    imb_from.layout,
    <span class="enum">VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL</span>,
    <span class="mcr">VK_QUEUE_FAMILY_IGNORED</span>, <span class="cmt">//无队列族所有权转移</span>
    <span class="mcr">VK_QUEUE_FAMILY_IGNORED</span>,
    image,
    {
        region.imageSubresource.aspectMask,
        region.imageSubresource.mipLevel,
        1,
        region.imageSubresource.baseArrayLayer,
        region.imageSubresource.layerCount }
};
<span class="kw">if</span> (imb_from.isNeeded)
    <span class="fn">vkCmdPipelineBarrier</span>(commandBuffer, imb_from.stage, <span class="enum">VK_PIPELINE_STAGE_TRANSFER_BIT</span>, 0,
        0, <span class="kw">nullptr</span>, 0, <span class="kw">nullptr</span>, 1, &amp;imageMemoryBarrier);
</pre>
<p>
    类似地，你该也能举一反三地明白拷贝后的图像管线屏障的意义了。简单修改<span class="var">imageMemoryBarrier</span>后，书写拷贝后的管线屏障：
</p>
<pre class="code">
<span class="kw">if</span> (imb_to.isNeeded) {
    imageMemoryBarrier.srcAccessMask = <span class="enum">VK_ACCESS_TRANSFER_WRITE_BIT</span>;
    imageMemoryBarrier.oldLayout = <span class="enum">VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL</span>;
    imageMemoryBarrier.dstAccessMask = imb_to.access;
    imageMemoryBarrier.newLayout = imb_to.layout;
    <span class="fn">vkCmdPipelineBarrier</span>(commandBuffer, <span class="enum">VK_PIPELINE_STAGE_TRANSFER_BIT</span>, imb_to.stage, 0,
        0, <span class="kw">nullptr</span>, 0, <span class="kw">nullptr</span>, 1, &amp;imageMemoryBarrier);
}
</pre>
<p>
    接着来看看重点（但没什么难点）的<a href="https://renderdoc.org/vkspec_chunked/chap20.html#vkCmdCopyBufferToImage">vkCmdCopyBufferToImage</a>(...)：
</p>
<table class="docutils align-default">
    <colgroup>
        <col style="width: 30%" />
        <col style="width: 70%" />
    </colgroup>
    <thead>
        <tr class="row-odd">
            <th class="head" colspan="2"><p><span class="kw">void</span> <span class="mcr">VKAPI_CALL</span> <a href="https://renderdoc.org/vkspec_chunked/chap20.html#vkCmdCopyBufferToImage">vkCmdCopyBufferToImage</a>(...) 的参数说明</p></th>
        </tr>
    </thead>
    <tbody>
        <tr class="row-even">
            <td><p><span class="type">VkCommandBuffer</span> commandBuffer</p></td>
            <td><p>命令缓冲区的handle</p></td>
        </tr>
        <tr class="row-odd">
            <td><p><span class="type">VkBuffer</span> srcBuffer</p></td>
            <td><p>源缓冲区</p></td>
        </tr>
        <tr class="row-even">
            <td><p><span class="type">VkImage</span> dstImage</p></td>
            <td><p>目标图像</p></td>
        </tr>
        <tr class="row-odd">
            <td><p><span class="type">VkImageLayout</span> dstImageLayout</p></td>
            <td><p>目标图像的内存布局</p></td>
        </tr>
        <tr class="row-even">
            <td><p><span class="type">uint32_t</span> regionCount</td>
            <td><p>要被拷贝的数据块的个数</p></td>
        </tr>
        <tr class="row-odd">
            <td><p><span class="kw">const</span> <span class="type">VkBufferImageCopy</span>* pRegions</p></td>
            <td><p>指向<span class="type">VkBufferImageCopy</span>类型的数组，用于指定要被拷贝的数据块</p></td>
        </tr>
    </tbody>
</table>
<ul>
    <li>
        <span class="type">VkBufferImageCopy</span>也适用于从图像将数据拷贝到缓冲区的<a href="https://renderdoc.org/vkspec_chunked/chap20.html#vkCmdCopyImageToBuffer">vkCmdCopyImageToBuffer</a>(...)命令。
    </li>
</ul>
<table class="docutils align-default">
    <colgroup>
        <col style="width: 30%" />
        <col style="width: 70%" />
    </colgroup>
    <thead>
        <tr class="row-odd">
            <th class="head" colspan="2"><p><span class="kw">struct</span> <a href="https://renderdoc.org/vkspec_chunked/chap20.html#VkBufferImageCopy">VkBufferImageCopy</a> 的成员说明</p></th>
        </tr>
    </thead>
    <tbody>
        <tr class="row-even">
            <td><p><span class="type">VkDeviceSize</span> bufferOffset</p></td>
            <td><p>要被拷贝的数据在源缓冲区中的起始位置，或数据被拷贝到目标缓冲区的起始位置，单位是字节</p></td>
        </tr>
        <tr class="row-odd">
            <td><p><span class="type">uint32_t</span> bufferRowLength</p></td>
            <td><p>将缓冲区从bufferOffset起始的部分视作图像，在此填入其宽度，单位是像素</p></td>
        </tr>
        <tr class="row-even">
            <td><p><span class="type">uint32_t</span> bufferImageHeight</p></td>
            <td><p>将缓冲区从bufferOffset起始的部分视作图像，在此填入其高度，单位是像素</p></td>
        </tr>
        <tr class="row-odd">
            <td><p><span class="type">VkImageSubresourceLayers</span> imageSubresource</p></td>
            <td><p>图像的子资源范围</p></td>
        </tr>
        <tr class="row-even">
            <td><p><span class="type">VkOffset3D</span> imageOffset</p></td>
            <td><p>要被拷贝的图像区域，或复制入数据的图像区域的起始位置，单位是像素</p></td>
        </tr>
        <tr class="row-odd">
            <td><p><span class="type">VkExtent3D</span> imageExtent</p></td>
            <td><p>要被拷贝的图像区域，或复制入数据的图像区域的大小，单位是像素</p></td>
        </tr>
    </tbody>
</table>
<ul>
    <li>
        <p>
            若bufferRowLength和bufferImageHeight都非0，那么无论是从缓冲区拷贝数据到图像，或从图像拷贝数据到缓冲区，bufferRowLength和bufferImageHeight都应该大于等于imageExtent指定的长宽。
        </p>
    </li>
    <li>
        <p>
            bufferRowLength大于imageExtent.width意味着有每行的数据之间有空余字节而非紧密排列，若<code>bufferRowLength * bufferImageHeight > imageExtent.width * imageExtent.length</code>则说明每个切面（可以拷贝自/拷贝到3D图像）之间有空余字节。
        </p>
    </li>
    <li>
        <p>
            若bufferRowLength和bufferImageHeight之一为0，等效于两者等于imageExtent指定的长宽，则复制自缓冲区的图像数据之间为紧密排列，而从图像复制进缓冲区的数据之间则不会补足空余字节。
        </p>
    </li>
</ul>
<table class="docutils align-default">
    <colgroup>
        <col style="width: 30%" />
        <col style="width: 70%" />
    </colgroup>
    <thead>
        <tr class="row-odd">
            <th class="head" colspan="2"><p><span class="kw">struct</span> <a href="https://renderdoc.org/vkspec_chunked/chap20.html#VkImageSubresourceLayers">VkImageSubresourceLayers</a> 的成员说明</p></th>
        </tr>
    </thead>
    <tbody>
        <tr class="row-even">
            <td><p><span class="type">VkImageAspectFlags</span> aspectMask</p></td>
            <td><p>所使用图像的层面</p></td>
        </tr>
        <tr class="row-odd">
            <td><p><span class="type">uint32_t</span> mipLevel</p></td>
            <td><p>mip等级</p></td>
        </tr>
        <tr class="row-even">
            <td><p><span class="type">uint32_t</span> baseArrayLayer</p></td>
            <td><p>初始图层</p></td>
        </tr>
        <tr class="row-odd">
            <td><p><span class="type">uint32_t</span> layerCount</p></td>
            <td><p>图层总数</p></td>
        </tr>
    </tbody>
</table>
<ul>
    <li>
        <p>
            <a href="https://renderdoc.org/vkspec_chunked/chap20.html#VkImageSubresourceLayers">VkImageSubresourceLayers</a>相比<a href="https://renderdoc.org/vkspec_chunked/chap20.html#VkImageSubresourceRange">VkImageSubresourceRange</a>少一个levelCount成员，关于<a href="https://renderdoc.org/vkspec_chunked/chap20.html#VkImageSubresourceLayers">VkImageSubresourceLayers</a>的解说请参考<a class="reference internal" href="Ch3-2%20%E5%9B%BE%E5%83%8F%E4%B8%8E%E7%BC%93%E5%86%B2%E5%8C%BA.html#image-view">Ch3-2中创建图像视图时涉及的关于VkImageSubresourceRange的解说</a>。
        </p>
    </li>
</ul>
<p>
    于是补全<span class="sfn">CmdCopyBufferToImage</span>(...)如下：
</p>
<pre class="code">
<span class="kw">static void</span> <span class="sfn">CmdCopyBufferToImage</span>(<span class="type">VkCommandBuffer</span> commandBuffer, <span class="type">VkBuffer</span> buffer, <span class="type">VkImage</span> image, <span class="kw">const</span> <span class="type">VkBufferImageCopy</span>&amp; region,
    <span class="type">imageMemoryBarrierParameterPack</span> imb_from, <span class="type">imageMemoryBarrierParameterPack</span> imb_to) {
    <span class="type">VkImageMemoryBarrier</span> imageMemoryBarrier = {
        <span class="enum">VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER</span>,
        <span class="kw">nullptr</span>,
        imb_from.access,
        <span class="enum">VK_ACCESS_TRANSFER_WRITE_BIT</span>,
        imb_from.layout,
        <span class="enum">VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL</span>,
        <span class="mcr">VK_QUEUE_FAMILY_IGNORED</span>, <span class="cmt">//无队列族所有权转移</span>
        <span class="mcr">VK_QUEUE_FAMILY_IGNORED</span>,
        image,
        {
            region.imageSubresource.aspectMask,
            region.imageSubresource.mipLevel,
            1,
            region.imageSubresource.baseArrayLayer,
            region.imageSubresource.layerCount }
    };
    <span class="kw">if</span> (imb_from.isNeeded)
        <span class="fn">vkCmdPipelineBarrier</span>(commandBuffer, imb_from.stage, <span class="enum">VK_PIPELINE_STAGE_TRANSFER_BIT</span>, 0,
            0, <span class="kw">nullptr</span>, 0, <span class="kw">nullptr</span>, 1, &amp;imageMemoryBarrier);
    <span class="fn">vkCmdCopyBufferToImage</span>(commandBuffer, buffer, image, <span class="enum">VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL</span>, 1, &amp;region);
    <span class="kw">if</span> (imb_to.isNeeded) {
        imageMemoryBarrier.srcAccessMask = <span class="enum">VK_ACCESS_TRANSFER_WRITE_BIT</span>;
        imageMemoryBarrier.oldLayout = <span class="enum">VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL</span>;
        imageMemoryBarrier.dstAccessMask = imb_to.access;
        imageMemoryBarrier.newLayout = imb_to.layout;
        <span class="fn">vkCmdPipelineBarrier</span>(commandBuffer, <span class="enum">VK_PIPELINE_STAGE_TRANSFER_BIT</span>, imb_to.stage, 0,
            0, <span class="kw">nullptr</span>, 0, <span class="kw">nullptr</span>, 1, &amp;imageMemoryBarrier);
    }
}
</pre></section>
<section id="cmdblitimage">
<h3>CmdBlitImage<a class="headerlink" href="#cmdblitimage" title="Permalink to this heading"></a></h3>
<p>
    来简单看一下我为<span class="sfn">CmdBlitImage</span>规定的各个参数：
</p>
<pre class="code">
<span class="kw">static void</span> <span class="sfn">CmdBlitImage</span>(
    <span class="type">VkCommandBuffer</span> commandBuffer, <span class="cmt">//命令缓冲区的handle</span>
    <span class="type">VkImage</span> image_src,             <span class="cmt">//作为数据来源的图像</span>
    <span class="type">VkImage</span> image_dst,             <span class="cmt">//接收数据的目标图像</span>
    <span class="kw">const</span> <span class="type">VkImageBlit</span>&amp; region,     <span class="cmt">//指定将源图像哪些部分拷贝到目标图像的哪些部分</span>
    <span class="type">imageMemoryBarrierParameterPack</span> imb_dst_from,
    <span class="type">imageMemoryBarrierParameterPack</span> imb_dst_to,
    <span class="type">VkFilter</span> filter = <span class="mcr">VK_FILTER_LINEAR</span>) { <span class="cmt">//若图像可能被放缩，在此指定滤波方式</span>
}
</pre>
<ul>
    <li>
        <p>
            imb_dst_from和imb_dst_to与<span class="sfn">CmdCopyBufferToImage</span>(...)的imb_from和imb_to作用一致。Blit涉及两张图像，本函数只处理目标图像的同步，这里为避免歧义而添加了后缀。
        </p>
    </li>
</ul>
<p>
    本函数不将源图像用作内存屏障的同步对象，一是出于将其用法设计得与<span class="sfn">CmdCopyBufferToImage</span>(...)类似的想法。
    <br>
    二是，在遇到要调用本函数的情况时，源图像的内存布局转换等，很可能由本函数前后的其他过程完成。比如，流水线中要将一张离屏渲染好的图像作为源图像，blit到交换链图像（像是，出于性能原因，只有UI以屏幕分辨率渲染，游戏主画面以较低分辨率渲染的情况），那么可以直接用渲染通道结束时的子通道依赖进行内存布局转换。
</p>
<p>
    接着，类似我们之前所做的，先将<span class="sfn">CmdBlitImage</span>(...)分成三个部分，并写好除了变量名外与<span class="sfn">CmdCopyBufferToImage</span>(...)中完全一致的管线屏障：
</p>
<pre class="code">
<span class="kw">static void</span> <span class="sfn">CmdBlitImage</span>(<span class="type">VkCommandBuffer</span> commandBuffer, <span class="type">VkImage</span> image_src, <span class="type">VkImage</span> image_dst, <span class="kw">const</span> <span class="type">VkImageBlit</span>&amp; region,
    <span class="type">imageMemoryBarrierParameterPack</span> imb_dst_from, <span class="type">imageMemoryBarrierParameterPack</span> imb_dst_to, <span class="type">VkFilter</span> filter = <span class="mcr">VK_FILTER_LINEAR</span>) {
    <span class="type">VkImageMemoryBarrier</span> imageMemoryBarrier = {
        <span class="enum">VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER</span>,
        <span class="kw">nullptr</span>,
        imb_dst_from.access,
        <span class="enum">VK_ACCESS_TRANSFER_WRITE_BIT</span>,
        imb_dst_from.layout,
        <span class="enum">VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL</span>,
        <span class="mcr">VK_QUEUE_FAMILY_IGNORED</span>,
        <span class="mcr">VK_QUEUE_FAMILY_IGNORED</span>,
        image,
        {
            region.imageSubresource.aspectMask,
            region.imageSubresource.mipLevel,
            1,
            region.imageSubresource.baseArrayLayer,
            region.imageSubresource.layerCount }
    };
    <span class="kw">if</span> (imb_dst_from.isNeeded)
        <span class="fn">vkCmdPipelineBarrier</span>(commandBuffer, imb_dst_from.stage, <span class="enum">VK_PIPELINE_STAGE_TRANSFER_BIT</span>, 0,
            0, <span class="kw">nullptr</span>, 0, <span class="kw">nullptr</span>, 1, &amp;imageMemoryBarrier);
    <span class="fn">vkCmdBlitImage</span>(<span class="cmt">/*待填充*/</span>);
    <span class="kw">if</span> (imb_dst_to.isNeeded) {
        imageMemoryBarrier.srcAccessMask = <span class="enum">VK_ACCESS_TRANSFER_WRITE_BIT</span>;
        imageMemoryBarrier.oldLayout = <span class="enum">VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL</span>;
        imageMemoryBarrier.dstAccessMask = imb_dst_to.access;
        imageMemoryBarrier.newLayout = imb_dst_to.layout;
        <span class="fn">vkCmdPipelineBarrier</span>(commandBuffer, <span class="enum">VK_PIPELINE_STAGE_TRANSFER_BIT</span>, imb_dst_to.stage, 0,
            0, <span class="kw">nullptr</span>, 0, <span class="kw">nullptr</span>, 1, &amp;imageMemoryBarrier);
    }
}
</pre>
<p>
    接着来看一下<a href="https://renderdoc.org/vkspec_chunked/chap20.html#vkCmdBlitImage">vkCmdBlitImage</a>(...)的用法：
</p>
    <table class="docutils align-default">
    <colgroup>
        <col style="width: 30%" />
        <col style="width: 70%" />
    </colgroup>
    <thead>
        <tr class="row-odd">
            <th class="head" colspan="2"><p><span class="kw">void</span> <span class="mcr">VKAPI_CALL</span> <a href="https://renderdoc.org/vkspec_chunked/chap20.html#vkCmdBlitImage">vkCmdBlitImage</a>(...) 的参数说明</p></th>
        </tr>
    </thead>
    <tbody>
        <tr class="row-even">
            <td><p><span class="type">VkCommandBuffer</span> commandBuffer</p></td>
            <td><p>命令缓冲区的handle</p></td>
        </tr>
        <tr class="row-odd">
            <td><p><span class="type">VkImage</span> srcImage</p></td>
            <td><p>源图像</p></td>
        </tr>
        <tr class="row-even">
            <td><p><span class="type">VkImageLayout</span> srcImageLayout</p></td>
            <td><p>源图像的内存布局</p></td>
        </tr>
        <tr class="row-odd">
            <td><p><span class="type">VkImage</span> dstImage</p></td>
            <td><p>目标图像</p></td>
        </tr>
        <tr class="row-even">
            <td><p><span class="type">VkImageLayout</span> dstImageLayout</p></td>
            <td><p>目标图像的内存布局</p></td>
        </tr>
        <tr class="row-odd">
            <td><p><span class="type">uint32_t</span> regionCount</td>
            <td><p>要被blit的数据块的个数</p></td>
        </tr>
        <tr class="row-even">
            <td><p><span class="kw">const</span> <span class="type">VkImageBlit</span>* pRegions</p></td>
            <td><p>指向<span class="type">VkImageBlit</span>类型的数组，用于指定要被blit的数据块</p></td>
        </tr>
        <tr class="row-odd">
            <td><p><span class="type">VkFilter</span> filter</td>
            <td><p>图像被放缩时的滤波方式，关于滤波方式，见<a class="reference internal" href="Ch3-7%20%E9%87%87%E6%A0%B7%E5%99%A8.html">Ch3-7 采样器</a></p></td>
        </tr>
    </tbody>
</table>
<table class="docutils align-default">
    <colgroup>
        <col style="width: 30%" />
        <col style="width: 70%" />
    </colgroup>
    <thead>
        <tr class="row-odd">
            <th class="head" colspan="2"><p><span class="kw">struct</span> <a href="https://renderdoc.org/vkspec_chunked/chap20.html#VkImageBlit">VkImageBlit</a> 的成员说明</p></th>
        </tr>
    </thead>
    <tbody>
        <tr class="row-even">
            <td><p><span class="type">VkImageSubresourceLayers</span> srcSubresource</p></td>
            <td><p>源图像的子资源范围</p></td>
        </tr>
        <tr class="row-odd">
            <td><p><span class="type">VkOffset3D</span> srcOffsets[2]</p></td>
            <td><p>源图像被blit的数据的边界</p></td>
        </tr>
        <tr class="row-even">
            <td><p><span class="type">VkImageSubresourceLayers</span> dstSubresource</p></td>
            <td><p>目标图像的子资源范围</p></td>
        </tr>
        <tr class="row-odd">
            <td><p><span class="type">VkOffset3D</span> dstOffsets[2]</p></td>
            <td><p>目标图像被blit入数据的边界</p></td>
        </tr>
    </tbody>
</table>
<ul>
    <li>
        <p>
            srcOffsets[0]的各分量未必要小于srcOffsets[1]的各分量，dstOffsets也是同理。
        </p>
    </li>
</ul>
<p>
    值得一提的是，blit命令不仅能放缩，通过妥当设置srcOffset，还能实现图像翻转，比如：
</p>
<pre class="code">
<span class="cmt">//若这是无翻转</span>
<span class="type">VkImageBlit</span> region_blit = {
    { <span class="enum">VK_IMAGE_ASPECT_COLOR_BIT</span>, 0, 0, 1 },
    { {}, { width, height, 1 } },
    { <span class="enum">VK_IMAGE_ASPECT_COLOR_BIT</span>, 0, 0, 1 },
    { {}, { width, height, 1 } }
};
<span class="cmt">//那么这是横向翻转</span>
<span class="type">VkImageBlit</span> region_blit = {
    { <span class="enum">VK_IMAGE_ASPECT_COLOR_BIT</span>, 0, 0, 1 },
    { { width }, { 0, height, 1 } },
    { <span class="enum">VK_IMAGE_ASPECT_COLOR_BIT</span>, 0, 0, 1 },
    { {}, { width, height, 1 } }
};
</pre>
<p>
    源图像的内存布局当然该是<span class="enum">VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL</span>，于是补全<span class="sfn">CmdBlitImage</span>(...)如下：
</p>
<pre class="code">
<span class="kw">static void</span> <span class="sfn">CmdBlitImage</span>(<span class="type">VkCommandBuffer</span> commandBuffer, <span class="type">VkImage</span> image_src, <span class="type">VkImage</span> image_dst, <span class="kw">const</span> <span class="type">VkImageBlit</span>&amp; region,
    <span class="type">imageMemoryBarrierParameterPack</span> imb_dst_from, <span class="type">imageMemoryBarrierParameterPack</span> imb_dst_to, <span class="type">VkFilter</span> filter = <span class="mcr">VK_FILTER_LINEAR</span>) {
    <span class="type">VkImageMemoryBarrier</span> imageMemoryBarrier = {
        <span class="enum">VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER</span>,
        <span class="kw">nullptr</span>,
        imb_dst_from.access,
        <span class="enum">VK_ACCESS_TRANSFER_WRITE_BIT</span>,
        imb_dst_from.layout,
        <span class="enum">VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL</span>,
        <span class="mcr">VK_QUEUE_FAMILY_IGNORED</span>,
        <span class="mcr">VK_QUEUE_FAMILY_IGNORED</span>,
        image,
        {
            region.imageSubresource.aspectMask,
            region.imageSubresource.mipLevel,
            1,
            region.imageSubresource.baseArrayLayer,
            region.imageSubresource.layerCount }
    };
    <span class="kw">if</span> (imb_dst_from.isNeeded)
        <span class="fn">vkCmdPipelineBarrier</span>(commandBuffer, imb_dst_from.stage, <span class="enum">VK_PIPELINE_STAGE_TRANSFER_BIT</span>, 0,
            0, <span class="kw">nullptr</span>, 0, <span class="kw">nullptr</span>, 1, &amp;imageMemoryBarrier);
    <span class="fn">vkCmdBlitImage</span>(commandBuffer,
        image_src, <span class="enum">VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL</span>,
        image_dst, <span class="enum">VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL</span>,
        1, &amp;region, filter);
    <span class="kw">if</span> (imb_dst_to.isNeeded) {
        imageMemoryBarrier.srcAccessMask = <span class="enum">VK_ACCESS_TRANSFER_WRITE_BIT</span>;
        imageMemoryBarrier.oldLayout = <span class="enum">VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL</span>;
        imageMemoryBarrier.dstAccessMask = imb_dst_to.access;
        imageMemoryBarrier.newLayout = imb_dst_to.layout;
        <span class="fn">vkCmdPipelineBarrier</span>(commandBuffer, <span class="enum">VK_PIPELINE_STAGE_TRANSFER_BIT</span>, imb_dst_to.stage, 0,
            0, <span class="kw">nullptr</span>, 0, <span class="kw">nullptr</span>, 1, &amp;imageMemoryBarrier);
    }
}
</pre></section>
</section>
<section id="id4">
<h2>从暂存缓冲区到交换链图像<a class="headerlink" href="#id4" title="Permalink to this heading"></a></h2>
<p>
    先来处理较为简单的分支。
    <br>
    于是乎，调用我们所写的<span class="sfn">CmdCopyBufferToImage</span>(...)：
</p>
<pre class="code">
<span class="kw">if</span> (blit) {
    <span class="cmt">/*待填充*/</span>
}
<span class="kw">else</span> {
    <span class="type">VkBufferImageCopy</span> region_copy = {
        <span class="cmt">/*待填充*/</span>
    };
    <span class="type">imageOperation</span>::<span class="sfn">CmdCopyBufferToImage</span>(commandBuffer,
        <span class="type">stagingBuffer</span>::<span class="sfn">Buffer_MainThread</span>(),
        <span class="type">graphicsBase</span>::<span class="sfn">Base</span>().<span class="fn">SwapchainImage</span>(<span class="type">graphicsBase</span>::<span class="sfn">Base</span>().<span class="fn">CurrentImageIndex</span>()),
        region_copy,
        { <span class="cmt">/*待填充*/</span> },
        { <span class="cmt">/*待填充*/</span> });
}
</pre>
<p>
    先前调用了<span class="type">stagingBuffer</span>::<span class="sfn">BufferData_MainThread</span>(...)将来源图像的数据紧密地存入了暂存缓冲区起始的位置。因而<span class="var">region_copy.bufferOffset</span>、<span class="var">region_copy.bufferRowLength</span>、<span class="var">region_copy.bufferImageHeight</span>皆为0。
    <br>
    图像数据被拷入交换链图像的起始位置，因而<span class="var">region_copy.imageOffset</span>皆0。
    <br>
    这个分支的前提是，所使用图像大小与交换链图像一致，于是<span class="var">region_copy</span>为：
</p>
<pre class="code">
<span class="type">VkBufferImageCopy</span> region_copy = {
    .imageSubresource = { <span class="enum">VK_IMAGE_ASPECT_COLOR_BIT</span>, 0, 0, 1 },
    .imageExtent = { imageExtent.width, imageExtent.height, 1 }
};
</pre>
<p>
    显然，因为这个函数旨在于程序开始时显示图像，所以交换链图像尚未被使用过，在管线屏障中没有需要被阻塞的命令，srcStageMask填写为<span class="enum">VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT</span>，则srcAccessMask必须为0。图像的初始内存布局则是<span class="enum">VK_IMAGE_LAYOUT_UNDEFINED</span>。
    <br>
    而之后，因为会用栅栏来同步，便不需要通过管线屏障来堵住后续命令的管线阶段。也无需注明访问操作的类型来确保交换链图像的资源可见性，因为虽然等待栅栏不会确保资源可见性，但是<a href="https://renderdoc.org/vkspec_chunked/chap34.html#vkQueuePresentKHR">vkQueuePresentKHR</a>(...)会，于是只需要指定最后的内存布局是<span class="enum">VK_IMAGE_LAYOUT_PRESENT_SRC_KHR</span>即可。
</p>
<pre class="code">
<span class="kw">if</span> (blit) {
    <span class="cmt">/*待填充*/</span>
}
<span class="kw">else</span> {
    <span class="type">VkBufferImageCopy</span> region_copy = {
        .imageSubresource = { <span class="enum">VK_IMAGE_ASPECT_COLOR_BIT</span>, 0, 0, 1 },
        .imageExtent = { imageExtent.width, imageExtent.height, 1 }
    };
    <span class="type">imageOperation</span>::<span class="sfn">CmdCopyBufferToImage</span>(commandBuffer,
        <span class="type">stagingBuffer</span>::<span class="sfn">Buffer_MainThread</span>(),
        <span class="type">graphicsBase</span>::<span class="sfn">Base</span>().<span class="fn">SwapchainImage</span>(<span class="type">graphicsBase</span>::<span class="sfn">Base</span>().<span class="fn">CurrentImageIndex</span>()),
        region_copy,
        { <span class="enum">VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT</span>, 0, <span class="enum">VK_IMAGE_LAYOUT_UNDEFINED</span> },
        { <span class="enum">VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT</span>, 0, <span class="enum">VK_IMAGE_LAYOUT_PRESENT_SRC_KHR </span> });
}
</pre>
<p>
    管线屏障是Vulkan学习过程中的一个难点，但会经常使用。
    <br>
    往下阅读之前，请确保你已经理解了此处管线屏障和图像内存屏障的作用。
</p></section>
<section id="id5">
<h2>从暂存缓冲区到暂存图像<a class="headerlink" href="#id5" title="Permalink to this heading"></a></h2>
<p>
    接着是较为复杂的分支。
    <br>
    我们首先试着创建能够与暂存缓冲区混叠的暂存图像：
</p>
<pre class="code">
<span class="cmt">/*待填充*/</span>
<span class="kw">if</span> (blit) {
    <span class="type">VkImage</span> image = <span class="type">stagingBuffer</span>::<span class="sfn">AliasedImage2d_MainThread</span>(imageFormat, imageExtent);
    <span class="cmt">/*待填充*/</span>
}
<span class="kw">else</span> {
    <span class="cmt">/*...*/</span>
}
</pre>
<p>
    你应该已经读过<span class="type">stagingBuffer</span>::<span class="fn">AliasedImage2d</span>(...)的用法了，若这里image为<span class="mcr">VK_NULL_HANDLE</span>，那么说明无法创建与暂存缓冲区混叠的图像，这可能有各种原因，比如图像大小不是某些数值的倍数，所以得有填充字节等等（而我们存入暂存缓冲区的数据当然是紧密排列的）。
    <br>
    无论如何，这时我们就得创建一张有独立设备内存的暂存图像，然后将图像数据从暂存缓冲区拷贝至其中。
    <br>
    于是产生两个分支：
</p>
<pre class="code">
<span class="cmt">/*待填充*/</span>
<span class="kw">if</span> (blit) {
    <span class="type">VkImage</span> image = <span class="type">stagingBuffer</span>::<span class="sfn">AliasedImage2d_MainThread</span>(imageFormat, imageExtent);
    <span class="kw">if</span> (image) {
        <span class="cmt">/*待填充*/</span>
    }
    <span class="kw">else</span> {
        <span class="cmt">/*待填充*/</span>
    }
    <span class="cmt">/*待填充*/</span>
}
<span class="kw">else</span> {
    <span class="cmt">/*...*/</span>
}
</pre>
<p>
    先来处理<span class="var">image</span>非0的分支，只需使用图像内存屏障，在正确的时机转换内存布局即可。
    <br>
    与暂存缓冲区混叠的图像，其内存布局为<span class="enum">VK_IMAGE_LAYOUT_PREINITIALIZED</span>，转换到<span class="enum">VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL</span>。
    <br>
    没有要被阻塞的命令，srcStageMask为<span class="enum">VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT</span>，srcAccessMask为0。注意没必要写<span class="enum">VK_PIPELINE_STAGE_HOST_BIT</span>和<span class="enum">VK_ACCESS_HOST_WRITE_BIT</span>，用<a href="https://renderdoc.org/vkspec_chunked/chap6.html#vkQueueSubmit">vkQueueSubmit</a>(...)提交命令缓冲区便足以确保资源可见性。
    <br>
    而dstStageMask为<span class="enum">VK_PIPELINE_STAGE_TRANSFER_BIT</span>，dstAccessMask当然是<span class="enum">VK_ACCESS_TRANSFER_READ_BIT</span>：
</p>
<pre class="code">
<span class="cmt">/*待填充*/</span>
<span class="kw">if</span> (blit) {
    <span class="type">VkImage</span> image = <span class="type">stagingBuffer</span>::<span class="sfn">AliasedImage2d_MainThread</span>(imageFormat, imageExtent);
    <span class="kw">if</span> (image) {
        <span class="type">VkImageMemoryBarrier</span> imageMemoryBarrier = {
            <span class="enum">VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER</span>,
            <span class="kw">nullptr</span>,
            0,
            <span class="enum">VK_ACCESS_TRANSFER_READ_BIT</span>,
            <span class="enum">VK_IMAGE_LAYOUT_PREINITIALIZED</span>,
            <span class="enum">VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL</span>,
            <span class="mcr">VK_QUEUE_FAMILY_IGNORED</span>,
            <span class="mcr">VK_QUEUE_FAMILY_IGNORED</span>,
            image,
            { <span class="enum">VK_IMAGE_ASPECT_COLOR_BIT</span>, 0, 1, 0, 1 }
        };
        <span class="fn">vkCmdPipelineBarrier</span>(commandBuffer, <span class="enum">VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT</span>, <span class="enum">VK_PIPELINE_STAGE_TRANSFER_BIT</span>, 0,
            0, <span class="kw">nullptr</span>, 0, <span class="kw">nullptr</span>, 1, &amp;imageMemoryBarrier);
    }
    <span class="kw">else</span> {
        <span class="cmt">/*待填充*/</span>
    }
    <span class="cmt">/*待填充*/</span>
}
<span class="kw">else</span> {
    <span class="cmt">/*...*/</span>
}
</pre>
<p>
    处理另一分支。于是来创建一张新的图像，因为数据会从暂存缓冲区复制进去，然后再从其中复制到交换链图像，因此图像用途同时包含<span class="enum">VK_IMAGE_USAGE_TRANSFER_SRC_BIT</span>和<span class="enum">VK_IMAGE_USAGE_TRANSFER_DST_BIT</span>，内存属性为最适合物理设备访问的<span class="enum">VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT</span>，其他参数应当无须说明：
</p>
<pre class="code">
<span class="type">imageMemory</span> imageMemory;
<span class="kw">if</span> (blit) {
    <span class="type">VkImage</span> image = <span class="type">stagingBuffer</span>::<span class="sfn">AliasedImage2d_MainThread</span>(imageFormat, imageExtent);
    <span class="kw">if</span> (image) {
        <span class="type">VkImageMemoryBarrier</span> imageMemoryBarrier = {
            <span class="enum">VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER</span>,
            <span class="kw">nullptr</span>,
            0,
            <span class="enum">VK_ACCESS_TRANSFER_READ_BIT</span>,
            <span class="enum">VK_IMAGE_LAYOUT_PREINITIALIZED</span>,
            <span class="enum">VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL</span>,
            <span class="mcr">VK_QUEUE_FAMILY_IGNORED</span>,
            <span class="mcr">VK_QUEUE_FAMILY_IGNORED</span>,
            image,
            { <span class="enum">VK_IMAGE_ASPECT_COLOR_BIT</span>, 0, 1, 0, 1 }
        };
        <span class="fn">vkCmdPipelineBarrier</span>(commandBuffer, <span class="enum">VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT</span>, <span class="enum">VK_PIPELINE_STAGE_TRANSFER_BIT</span>, 0,
            0, <span class="kw">nullptr</span>, 0, <span class="kw">nullptr</span>, 1, &amp;imageMemoryBarrier);
    }
    <span class="kw">else</span> {
        <span class="type">VkImageCreateInfo</span> imageCreateInfo = {
            .imageType = <span class="enum">VK_IMAGE_TYPE_2D</span>,
            .format = imageFormat,
            .extent = { imageExtent.width, imageExtent.height, 1 },
            .mipLevels = 1,
            .arrayLayers = 1,
            .samples = <span class="enum">VK_SAMPLE_COUNT_1_BIT</span>,
            .usage = <span class="enum">VK_IMAGE_USAGE_TRANSFER_SRC_BIT</span> | <span class="enum">VK_IMAGE_USAGE_TRANSFER_DST_BIT</span>
        };
        imageMemory.<span class="fn">Create</span>(imageCreateInfo, <span class="enum">VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT</span>);
        <span class="cmt">/*待填充*/</span>
    }
    <span class="cmt">/*待填充*/</span>
}
<span class="kw">else</span> {
    <span class="cmt">/*...*/</span>
}
</pre>
<ul>
    <li>
        <p>
            为确保代表新图像的对象<span class="var">imageMemory</span>在提交并执行完命令前不被析构，必须将其定义在一堆分支的语句块之前。
        </p>
    </li>
</ul>
<p>
    接着调用<span class="sfn">CmdCopyBufferToImage</span>(...)将数据从暂存缓冲区拷贝进去，图像内存屏障需要确保在被之后的blit命令读取前完成内存布局转换，参数也就不言而喻了：
</p>
<pre class="code">
<span class="type">imageMemory</span> imageMemory;
<span class="kw">if</span> (blit) {
    <span class="type">VkImage</span> image = <span class="type">stagingBuffer</span>::<span class="sfn">AliasedImage2d_MainThread</span>(imageFormat, imageExtent);
    <span class="kw">if</span> (image) {
        <span class="type">VkImageMemoryBarrier</span> imageMemoryBarrier = {
            <span class="enum">VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER</span>,
            <span class="kw">nullptr</span>,
            0,
            <span class="enum">VK_ACCESS_TRANSFER_READ_BIT</span>,
            <span class="enum">VK_IMAGE_LAYOUT_PREINITIALIZED</span>,
            <span class="enum">VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL</span>,
            <span class="mcr">VK_QUEUE_FAMILY_IGNORED</span>,
            <span class="mcr">VK_QUEUE_FAMILY_IGNORED</span>,
            image,
            { <span class="enum">VK_IMAGE_ASPECT_COLOR_BIT</span>, 0, 1, 0, 1 }
        };
        <span class="fn">vkCmdPipelineBarrier</span>(commandBuffer, <span class="enum">VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT</span>, <span class="enum">VK_PIPELINE_STAGE_TRANSFER_BIT</span>, 0,
            0, <span class="kw">nullptr</span>, 0, <span class="kw">nullptr</span>, 1, &amp;imageMemoryBarrier);
    }
    <span class="kw">else</span> {
        <span class="type">VkImageCreateInfo</span> imageCreateInfo = {
            .imageType = <span class="enum">VK_IMAGE_TYPE_2D</span>,
            .format = imageFormat,
            .extent = { imageExtent.width, imageExtent.height, 1 },
            .mipLevels = 1,
            .arrayLayers = 1,
            .samples = <span class="enum">VK_SAMPLE_COUNT_1_BIT</span>,
            .usage = <span class="enum">VK_IMAGE_USAGE_TRANSFER_SRC_BIT</span> | <span class="enum">VK_IMAGE_USAGE_TRANSFER_DST_BIT</span>
        };
        imageMemory.<span class="fn">Create</span>(imageCreateInfo, <span class="enum">VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT</span>);
        <span class="type">VkBufferImageCopy</span> region_copy = {
            .imageSubresource = { <span class="enum">VK_IMAGE_ASPECT_COLOR_BIT</span>, 0, 0, 1 },
            .imageExtent = imageCreateInfo.extent
        };
        <span class="type">imageOperation</span>::<span class="sfn">CmdCopyBufferToImage</span>(commandBuffer,
            <span class="type">stagingBuffer</span>::<span class="sfn">Buffer_MainThread</span>(),
            imageMemory.<span class="fn">Image</span>(),
            region_copy,
            { <span class="enum">VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT</span>, 0, <span class="enum">VK_IMAGE_LAYOUT_UNDEFINED</span> },
            { <span class="enum">VK_PIPELINE_STAGE_TRANSFER_BIT</span>, <span class="enum">VK_ACCESS_TRANSFER_READ_BIT</span>, <span    class="enum">VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL</span> });

        <span class="cmt">//将image赋值为imageMemory.Image()以便后续操作</span>
        image = imageMemory.<span class="fn">Image</span>();
    }
    <span class="cmt">/*待填充*/</span>
}
<span class="kw">else</span> {
    <span class="cmt">/*...*/</span>
}
</pre></section>
<section id="blit">
<h2>从暂存图像blit到交换链图像<a class="headerlink" href="#blit" title="Permalink to this heading"></a></h2>
<p>
    现在，<span class="var">image</span>一定记录了一个有效的handle。
    调用我们所写的<span class="sfn">CmdBlitImage</span>(...)将图像数据从<span class="var">image</span>中blit到交换链图像：
</p>
<pre class="code">
<span class="kw">if</span> (blit) {
    <span class="type">VkImage</span> image = <span class="type">stagingBuffer</span>::<span class="sfn">AliasedImage2d_MainThread</span>(imageFormat, imageExtent);
    <span class="kw">if</span> (image) {
        <span class="cmt">/*...*/</span>
    }
    <span class="kw">else</span> {
        <span class="cmt">/*...*/</span>
    }
    <span class="type">VkImageBlit</span> region_blit = {
        { <span class="enum">VK_IMAGE_ASPECT_COLOR_BIT</span>, 0, 0, 1 },
        { {}, { <span class="type">int32_t</span>(imageExtent.width), <span class="type">int32_t</span>(imageExtent.height), 1 } },
        { <span class="enum">VK_IMAGE_ASPECT_COLOR_BIT</span>, 0, 0, 1 },
        { {}, { <span class="type">int32_t</span>(swapchainImageSize.width), <span class="type">int32_t</span>(swapchainImageSize.height), 1 } }
    };
    <span class="type">imageOperation</span>::<span class="sfn">CmdBlitImage</span>(commandBuffer,
        image,
        <span class="type">graphicsBase</span>::<span class="sfn">Base</span>().<span class="fn">SwapchainImage</span>(<span class="type">graphicsBase</span>::<span class="sfn">Base</span>().<span class="fn">CurrentImageIndex</span>()),
        region_blit,
        { <span class="enum">VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT</span>, 0, <span class="enum">VK_IMAGE_LAYOUT_UNDEFINED</span> },
        { <span class="enum">VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT</span>, 0, <span class="enum">VK_IMAGE_LAYOUT_PRESENT_SRC_KHR</span> }, <span class="enum">VK_FILTER_LINEAR</span>);
}
<span class="kw">else</span> {
    <span class="cmt">/*...*/</span>
}
</pre>
<p>
    根据你的需要指定滤波方式。
    <br>
    最后，整个<span class="fn">BootScreen</span>(...)如下：
</p>
<pre class="code">
<span class="kw">void</span> <span class="fn">BootScreen</span>(<span class="kw">const char</span>* imagePath, <span class="type">VkFormat</span> imageFormat) {
    <span class="type">VkExtent2D</span> imageExtent;
    std::<span class="type">unique_ptr</span>&lt;<span class="type">uint8_t</span>[]&gt; pImageData = <span class="type">texture2d</span>::<span class="sfn">LoadFile</span>(imagePath, imageExtent, <span class="fn">FormatInfo</span>(imageFormat));
    <span class="kw">if</span> (!pImageData)
        <span class="kw">return</span>;
    <span class="type">stagingBuffer</span>::<span class="sfn">BufferData_MainThread</span>(pImageData.<span class="fn">get</span>(), <span class="fn">FormatInfo</span>(imageFormat).sizePerPixel * imageExtent.width * imageExtent.height);

    <span class="type">semaphore</span> semaphore_imageIsAvailable;
    <span class="type">fence</span> fence;
    <span class="type">commandBuffer</span> commandBuffer;
    <span class="type">graphicsBase</span>::<span class="sfn">Plus</span>().<span class="fn">CommandPool_Graphics</span>().<span class="fn">AllocateBuffers</span>(commandBuffer);

    <span class="type">graphicsBase</span>::<span class="sfn">Base</span>().<span class="fn">SwapImage</span>(semaphore_imageIsAvailable);
    commandBuffer.<span class="fn">Begin</span>(<span class="enum">VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT</span>);
    <span class="type">VkExtent2D</span> swapchainImageSize = <span class="type">graphicsBase</span>::<span class="sfn">Base</span>().<span class="fn">SwapchainCreateInfo</span>().imageExtent;
    <span class="kw">bool</span> blit =
        imageExtent.width != swapchainImageSize.width ||
        imageExtent.height != swapchainImageSize.height ||
        imageFormat != <span class="type">graphicsBase</span>::<span class="sfn">Base</span>().<span class="fn">SwapchainCreateInfo</span>().imageFormat;
    <span class="type">imageMemory</span> imageMemory;
    <span class="kw">if</span> (blit) {
        <span class="type">VkImage</span> image = <span class="type">stagingBuffer</span>::<span class="sfn">AliasedImage2d_MainThread</span>(imageFormat, imageExtent);
        <span class="kw">if</span> (image) {
            <span class="type">VkImageMemoryBarrier</span> imageMemoryBarrier = {
                <span class="enum">VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER</span>,
                <span class="kw">nullptr</span>,
                0,
                <span class="enum">VK_ACCESS_TRANSFER_READ_BIT</span>,
                <span class="enum">VK_IMAGE_LAYOUT_PREINITIALIZED</span>,
                <span class="enum">VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL</span>,
                <span class="mcr">VK_QUEUE_FAMILY_IGNORED</span>,
                <span class="mcr">VK_QUEUE_FAMILY_IGNORED</span>,
                image,
                { <span class="enum">VK_IMAGE_ASPECT_COLOR_BIT</span>, 0, 1, 0, 1 }
            };
            <span class="fn">vkCmdPipelineBarrier</span>(commandBuffer, <span class="enum">VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT</span>, <span class="enum">VK_PIPELINE_STAGE_TRANSFER_BIT</span>, 0,
            0, <span class="kw">nullptr</span>, 0, <span class="kw">nullptr</span>, 1, &amp;imageMemoryBarrier);
        }
        <span class="kw">else</span> {
            <span class="type">VkImageCreateInfo</span> imageCreateInfo = {
                .imageType = <span class="enum">VK_IMAGE_TYPE_2D</span>,
                .format = imageFormat,
                .extent = { imageExtent.width, imageExtent.height, 1 },
                .mipLevels = 1,
                .arrayLayers = 1,
                .samples = <span class="enum">VK_SAMPLE_COUNT_1_BIT</span>,
                .usage = <span class="enum">VK_IMAGE_USAGE_TRANSFER_SRC_BIT</span> | <span class="enum">VK_IMAGE_USAGE_TRANSFER_DST_BIT</span>
            };
            imageMemory.<span class="fn">Create</span>(imageCreateInfo, <span class="enum">VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT</span>);
            <span class="type">VkBufferImageCopy</span> region_copy = {
                .imageSubresource = { <span class="enum">VK_IMAGE_ASPECT_COLOR_BIT</span>, 0, 0, 1 },
                .imageExtent = imageCreateInfo.extent
            };
            <span class="type">imageOperation</span>::<span class="sfn">CmdCopyBufferToImage</span>(commandBuffer,
                <span class="type">stagingBuffer</span>::<span class="sfn">Buffer_MainThread</span>(),
                imageMemory.<span class="fn">Image</span>(),
                region_copy,
                { <span class="enum">VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT</span>, 0, <span class="enum">VK_IMAGE_LAYOUT_UNDEFINED</span> },
                { <span class="enum">VK_PIPELINE_STAGE_TRANSFER_BIT</span>, <span class="enum">VK_ACCESS_TRANSFER_READ_BIT</span>, <span    class="enum">VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL</span> });

            image = imageMemory.<span class="fn">Image</span>();
        }
        <span class="type">VkImageBlit</span> region_blit = {
            { <span class="enum">VK_IMAGE_ASPECT_COLOR_BIT</span>, 0, 0, 1 },
            { {}, { <span class="type">int32_t</span>(imageExtent.width), <span class="type">int32_t</span>(imageExtent.height), 1 } },
            { <span class="enum">VK_IMAGE_ASPECT_COLOR_BIT</span>, 0, 0, 1 },
            { {}, { <span class="type">int32_t</span>(swapchainImageSize.width), <span class="type">int32_t</span>(swapchainImageSize.height), 1 } }
        };
        <span class="type">imageOperation</span>::<span class="sfn">CmdBlitImage</span>(commandBuffer,
            image,
            <span class="type">graphicsBase</span>::<span class="sfn">Base</span>().<span class="fn">SwapchainImage</span>(<span     class="type">graphicsBase</span>::<span class="sfn">Base</span>().<span class="fn">CurrentImageIndex</span>()),
            region_blit,
            { <span class="enum">VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT</span>, 0, <span class="enum">VK_IMAGE_LAYOUT_UNDEFINED</span> },
            { <span class="enum">VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT</span>, 0, <span class="enum">VK_IMAGE_LAYOUT_PRESENT_SRC_KHR</span> }, <span class="enum">VK_FILTER_LINEAR</span>);
    }
    <span class="kw">else</span> {
        <span class="type">VkBufferImageCopy</span> region_copy = {
            .imageSubresource = { <span class="enum">VK_IMAGE_ASPECT_COLOR_BIT</span>, 0, 0, 1 },
            .imageExtent = { imageExtent.width, imageExtent.height, 1 }
        };
        <span class="type">imageOperation</span>::<span class="sfn">CmdCopyBufferToImage</span>(commandBuffer,
            <span class="type">stagingBuffer</span>::<span class="sfn">Buffer_MainThread</span>(),
            <span class="type">graphicsBase</span>::<span class="sfn">Base</span>().<span class="fn">SwapchainImage</span>(<span     class="type">graphicsBase</span>::<span class="sfn">Base</span>().<span class="fn">CurrentImageIndex</span>()),
            region_copy,
            { <span class="enum">VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT</span>, 0, <span class="enum">VK_IMAGE_LAYOUT_UNDEFINED</span> },
            { <span class="enum">VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT</span>, 0, <span class="enum">VK_IMAGE_LAYOUT_PRESENT_SRC_KHR </span> });
    }
    commandBuffer.<span class="fn">End</span>();

    <span class="type">VkPipelineStageFlags</span> waitDstStage = <span class="enum">VK_PIPELINE_STAGE_TRANSFER_BIT</span>;
    <span class="type">VkSubmitInfo</span> submitInfo = {
        .waitSemaphoreCount = 1,
        .pWaitSemaphores = semaphore_imageIsAvailable.<span class="fn">Address</span>(),
        .pWaitDstStageMask = &amp;waitDstStage,
        .commandBufferCount = 1,
        .pCommandBuffers = commandBuffer.<span class="fn">Address</span>()
    };
    <span class="type">graphicsBase</span>::<span class="sfn">Base</span>().<span class="fn">SubmitCommandBuffer_Graphics</span>(submitInfo, fence);
    fence.<span class="fn">WaitAndReset</span>();
    <span class="type">graphicsBase</span>::<span class="sfn">Base</span>().<span class="fn">PresentImage</span>();

    <span class="type">graphicsBase</span>::<span class="sfn">Plus</span>().<span class="fn">CommandPool_Graphics</span>().<span class="fn">FreeBuffers</span>(commandBuffer);
}
</pre>
<p>
    我们在先前绘制三角形的主函数里，初始化窗口后调用它，并刻意让主线程睡眠1秒（以免立刻跳掉）来查看效果：
</p>
<pre class="code">
<span class="kw">int</span> <span class="fn">main</span>() {
    <span class="kw">if</span> (!<span class="fn">InitializeWindow</span>({ 1280, 720 }))
        <span class="kw">return</span> -1;
    easyVulkan::<span class="fn">BootScreen</span>(<span class="cmt">/*你指定的图片路径*/</span>, <span class="enum">VK_FORMAT_R8G8B8A8_UNORM</span>);
    std::this_thread::<span class="sfn">sleep_for</span>(std::chrono::<span class="type">seconds</span>(1));<span class="cmt">//需要#include &lt;thread&gt;</span>
    <span class="cmt">/*...*/</span>
}
</pre>
<p>
    填写文件路径并运行程序，分别试试与窗口等大、与窗口大小不同的图像以测试不同的程序分支。
    <br>
    注意如果是透明图片，最终呈现到窗口后，透明度会丢失。而由stb_image读取png得到的图像数据，A通道值为0的像素点，其RGB通道未必为0，如有必要则自行对数据进行处理。
</p></section>
</section>


           </div>
          </div>
          <footer><div class="rst-footer-buttons" role="navigation" aria-label="Footer">
        <a href="Ch7-5%20%E5%88%9D%E8%AF%86Uniform%E7%BC%93%E5%86%B2%E5%8C%BA.html" class="btn btn-neutral float-left" title="Ch7-5 初识Uniform缓冲区" accesskey="p" rel="prev"><span class="fa fa-arrow-circle-left" aria-hidden="true"></span> 上一页</a>
        <a href="Ch7-7%20%E4%BD%BF%E7%94%A8%E8%B4%B4%E5%9B%BE.html" class="btn btn-neutral float-right" title="Ch7-7 使用贴图" accesskey="n" rel="next">下一页 <span class="fa fa-arrow-circle-right" aria-hidden="true"></span></a>
    </div>

  <hr/>

  <div role="contentinfo">
    <p>&#169; 版权所有 2021-2024, Qiao YeCheng.</p>
  </div>

  利用 <a href="https://www.sphinx-doc.org/">Sphinx</a> 构建，使用了 
    <a href="https://github.com/readthedocs/sphinx_rtd_theme">主题</a>
    由 <a href="https://readthedocs.org">Read the Docs</a>开发.
   

</footer>
        </div>
      </div>
    </section>
  </div>
  <script>
      jQuery(function () {
          SphinxRtdTheme.Navigation.enable(true);
      });
  </script> 

</body>
</html>